This commit is contained in:
2025-06-16 15:24:27 +02:00
commit 83a46b2fc4
1452 changed files with 214261 additions and 0 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,3 @@
fileFormatVersion: 2
guid: 251338e67afb4cefa38da924f8c50a6e
timeCreated: 1628851818

View File

@ -0,0 +1,188 @@
// for Unity 2020+ we use ILPostProcessor.
// only automatically invoke it for older versions.
#if !UNITY_2020_3_OR_NEWER
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Mono.CecilX;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;
using UnityAssembly = UnityEditor.Compilation.Assembly;
namespace Mirror.Weaver
{
public static class CompilationFinishedHook
{
// needs to be the same as Weaver.MirrorAssemblyName!
const string MirrorRuntimeAssemblyName = "Mirror";
const string MirrorWeaverAssemblyName = "Mirror.Weaver";
// global weaver define so that tests can use it
internal static Weaver 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 whether Weaver errors are reported direct to the Unity console (tests enable this)
public static bool UnityLogEnabled = true;
[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]);
}
}
EditorUtility.RequestScriptReload();
}
static Assembly FindCompilationPipelineAssembly(string assemblyName) =>
CompilationPipeline.GetAssemblies().First(assembly => assembly.name == assemblyName);
static bool CompilerMessagesContainError(CompilerMessage[] messages) =>
messages.Any(msg => msg.type == CompilerMessageType.Error);
public 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 (test ones still need to be weaved)
if (assemblyPath.Contains("-Editor") ||
(assemblyPath.Contains(".Editor") && !assemblyPath.Contains(".Tests")))
{
return;
}
// skip Mirror.dll because CompilationFinishedHook can't weave itself.
// this would cause a sharing violation.
// skip Mirror.Weaver.dll too.
string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
if (assemblyName == MirrorRuntimeAssemblyName || assemblyName == MirrorWeaverAssemblyName)
{
return;
}
// find Mirror.dll
Assembly mirrorAssembly = FindCompilationPipelineAssembly(MirrorRuntimeAssemblyName);
if (mirrorAssembly == null)
{
Debug.LogError("Failed to find Mirror runtime assembly");
return;
}
string mirrorRuntimeDll = mirrorAssembly.outputPath;
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 = GetDependencyPaths(assemblyPath);
dependencyPaths.Add(Path.GetDirectoryName(mirrorRuntimeDll));
dependencyPaths.Add(Path.GetDirectoryName(unityEngineCoreModuleDLL));
if (!WeaveFromFile(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> GetDependencyPaths(string assemblyPath)
{
// build directory list for later asm/symbol resolving using CompilationPipeline refs
HashSet<string> dependencyPaths = new HashSet<string>
{
Path.GetDirectoryName(assemblyPath)
};
foreach (Assembly assembly in CompilationPipeline.GetAssemblies())
{
if (assembly.outputPath == assemblyPath)
{
foreach (string reference in assembly.compiledAssemblyReferences)
{
dependencyPaths.Add(Path.GetDirectoryName(reference));
}
}
}
return dependencyPaths;
}
// helper function to invoke Weaver with an AssemblyDefinition from a
// file path, with dependencies added.
static bool WeaveFromFile(string assemblyPath, string[] dependencies)
{
// resolve assembly from stream
using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath, new ReaderParameters{ ReadWrite = true, ReadSymbols = true, AssemblyResolver = asmResolver }))
{
// add this assembly's path and unity's assembly path
asmResolver.AddSearchDirectory(Path.GetDirectoryName(assemblyPath));
asmResolver.AddSearchDirectory(Helpers.UnityEngineDllDirectoryName());
// add dependencies
if (dependencies != null)
{
foreach (string path in dependencies)
{
asmResolver.AddSearchDirectory(path);
}
}
// create weaver with logger
weaver = new Weaver(new CompilationFinishedLogger());
if (weaver.Weave(assembly, asmResolver, out bool modified))
{
// write changes to file if modified
if (modified)
assembly.Write(new WriterParameters{WriteSymbols = true});
return true;
}
return false;
}
}
}
}
#endif

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,31 @@
// logger for compilation finished hook.
// where we need a callback and Debug.Log.
// for Unity 2020+ we use ILPostProcessor.
#if !UNITY_2020_3_OR_NEWER
using Mono.CecilX;
using UnityEngine;
namespace Mirror.Weaver
{
public class CompilationFinishedLogger : Logger
{
public void Warning(string message) => Warning(message, null);
public void Warning(string message, MemberReference mr)
{
if (mr != null) message = $"{message} (at {mr})";
if (CompilationFinishedHook.UnityLogEnabled) Debug.LogWarning(message);
CompilationFinishedHook.OnWeaverWarning?.Invoke(message);
}
public void Error(string message) => Error(message, null);
public void Error(string message, MemberReference mr)
{
if (mr != null) message = $"{message} (at {mr})";
if (CompilationFinishedHook.UnityLogEnabled) Debug.LogError(message);
CompilationFinishedHook.OnWeaverError?.Invoke(message);
}
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 47026732f0fa475c94bd1dd41f1de559
timeCreated: 1629379868

View File

@ -0,0 +1,44 @@
#if !UNITY_2020_3_OR_NEWER
// make sure we weaved successfully when entering play mode.
using UnityEditor;
using UnityEngine;
namespace Mirror
{
public class EnterPlayModeSettingsCheck : MonoBehaviour
{
[InitializeOnLoadMethod]
static void OnInitializeOnLoad()
{
// Hook this event to see if we have a good weave every time
// user attempts to enter play mode or tries to do a build
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
static void OnPlayModeStateChanged(PlayModeStateChange state)
{
// Per Unity docs, this fires "when exiting edit mode before the Editor is in play mode".
// This doesn't fire when closing the editor.
if (state == PlayModeStateChange.ExitingEditMode)
{
// Check if last weave result was successful
if (!SessionState.GetBool("MIRROR_WEAVE_SUCCESS", false))
{
// Last weave result was a failure...try to weave again
// Faults will show in the console that may have been cleared by "Clear on Play"
SessionState.SetBool("MIRROR_WEAVE_SUCCESS", true);
Weaver.CompilationFinishedHook.WeaveExistingAssemblies();
// Did that clear things up for us?
if (!SessionState.GetBool("MIRROR_WEAVE_SUCCESS", false))
{
// Nope, still failed, and console has the issues logged
Debug.LogError("Can't enter play mode until weaver issues are resolved.");
EditorApplication.isPlaying = false;
}
}
}
}
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b73d0f106ba84aa983baa5142b08a0a9
timeCreated: 1628851346

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 09082db63d1d48d9ab91320165c1b684
timeCreated: 1628859005

View File

@ -0,0 +1,31 @@
// tests use WeaveAssembler, which uses AssemblyBuilder to Build().
// afterwards ILPostProcessor weaves the build.
// this works on windows, but build() does not run ILPP on mac atm.
// we need to manually invoke ILPP with an assembly from file.
//
// this is in Weaver folder becuase CompilationPipeline can only be accessed
// from assemblies with the name "Unity.*.CodeGen"
using System.IO;
using Unity.CompilationPipeline.Common.ILPostProcessing;
namespace Mirror.Weaver
{
public class CompiledAssemblyFromFile : ICompiledAssembly
{
readonly string assemblyPath;
public string Name => Path.GetFileNameWithoutExtension(assemblyPath);
public string[] References { get; set; }
public string[] Defines { get; set; }
public InMemoryAssembly InMemoryAssembly { get; }
public CompiledAssemblyFromFile(string assemblyPath)
{
this.assemblyPath = assemblyPath;
byte[] peData = File.ReadAllBytes(assemblyPath);
string pdbFileName = Path.GetFileNameWithoutExtension(assemblyPath) + ".pdb";
byte[] pdbData = File.ReadAllBytes(Path.Combine(Path.GetDirectoryName(assemblyPath), pdbFileName));
InMemoryAssembly = new InMemoryAssembly(peData, pdbData);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9009d1db4ed44f6694a92bf8ad7738e9
timeCreated: 1630129423

View File

@ -0,0 +1,199 @@
// based on paul's resolver from
// https://github.com/MirageNet/Mirage/commit/def64cd1db525398738f057b3d1eb1fe8afc540c?branch=def64cd1db525398738f057b3d1eb1fe8afc540c&diff=split
//
// an assembly resolver's job is to open an assembly in case we want to resolve
// a type from it.
//
// for example, while weaving MyGame.dll: if we want to resolve ArraySegment<T>,
// then we need to open and resolve from another assembly (CoreLib).
//
// using DefaultAssemblyResolver with ILPostProcessor throws Exceptions in
// WeaverTypes.cs when resolving anything, for example:
// ArraySegment<T> in Mirror.Tests.Dll.
//
// we need a custom resolver for ILPostProcessor.
#if UNITY_2020_3_OR_NEWER
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Threading;
using Mono.CecilX;
using Unity.CompilationPipeline.Common.ILPostProcessing;
namespace Mirror.Weaver
{
class ILPostProcessorAssemblyResolver : IAssemblyResolver
{
readonly string[] assemblyReferences;
// originally we used Dictionary + lock.
// Resolve() is called thousands of times for large projects.
// ILPostProcessor is multithreaded, so best to use ConcurrentDictionary without the lock here.
readonly ConcurrentDictionary<string, AssemblyDefinition> assemblyCache =
new ConcurrentDictionary<string, AssemblyDefinition>();
// Resolve() calls FindFile() every time.
// thousands of times for String => mscorlib alone in large projects.
// cache the results! ILPostProcessor is multithreaded, so use a ConcurrentDictionary here.
readonly ConcurrentDictionary<string, string> fileNameCache =
new ConcurrentDictionary<string, string>();
readonly ICompiledAssembly compiledAssembly;
AssemblyDefinition selfAssembly;
readonly Logger Log;
public ILPostProcessorAssemblyResolver(ICompiledAssembly compiledAssembly, Logger Log)
{
this.compiledAssembly = compiledAssembly;
assemblyReferences = compiledAssembly.References;
this.Log = Log;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
// Cleanup
}
public AssemblyDefinition Resolve(AssemblyNameReference name) =>
Resolve(name, new ReaderParameters(ReadingMode.Deferred));
// here is an example on when this is called:
// Player : NetworkBehaviour has a [SyncVar] of type String.
// Weaver's SyncObjectInitializer checks if ImplementsSyncObject()
// which needs to resolve the type 'String' from mscorlib.
// Resolve() lives in CecilX.MetadataResolver.Resolve()
// which calls assembly_resolver.Resolve().
// which uses our ILPostProcessorAssemblyResolver here.
//
// for large projects, this is called thousands of times for mscorlib alone.
// initially ILPostProcessorAssemblyResolver took 30x longer than with CompilationFinishedHook.
// we need to cache and speed up everything we can here!
public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
{
if (name.Name == compiledAssembly.Name)
return selfAssembly;
// cache FindFile.
// in large projects, this is called thousands(!) of times for String=>mscorlib alone.
// reduces a single String=>mscorlib resolve from 0.771ms to 0.015ms.
// => 50x improvement in TypeReference.Resolve() speed!
// => 22x improvement in Weaver speed!
if (!fileNameCache.TryGetValue(name.Name, out string fileName))
{
fileName = FindFile(name.Name);
fileNameCache.TryAdd(name.Name, fileName);
}
if (fileName == null)
{
// returning null will throw exceptions in our weaver where.
// let's make it obvious why we returned null for easier debugging.
// NOTE: if this fails for "System.Private.CoreLib":
// ILPostProcessorReflectionImporter fixes it!
Log.Warning($"ILPostProcessorAssemblyResolver.Resolve: Failed to find file for {name}");
return null;
}
// try to get cached assembly by filename + writetime
DateTime lastWriteTime = File.GetLastWriteTime(fileName);
string cacheKey = fileName + lastWriteTime;
if (assemblyCache.TryGetValue(cacheKey, out AssemblyDefinition result))
return result;
// otherwise resolve and cache a new assembly
parameters.AssemblyResolver = this;
MemoryStream ms = MemoryStreamFor(fileName);
string pdb = fileName + ".pdb";
if (File.Exists(pdb))
parameters.SymbolStream = MemoryStreamFor(pdb);
AssemblyDefinition assemblyDefinition = AssemblyDefinition.ReadAssembly(ms, parameters);
assemblyCache.TryAdd(cacheKey, assemblyDefinition);
return assemblyDefinition;
}
// find assemblyname in assembly's references
string FindFile(string name)
{
// perhaps the type comes from a .dll or .exe
// check both in one call without Linq instead of iterating twice like originally
foreach (string r in assemblyReferences)
{
if (Path.GetFileNameWithoutExtension(r) == name)
return r;
}
// this is called thousands(!) of times.
// constructing strings only once saves ~0.1ms per call for mscorlib.
string dllName = name + ".dll";
// Unfortunately the current ICompiledAssembly API only provides direct references.
// It is very much possible that a postprocessor ends up investigating a type in a directly
// referenced assembly, that contains a field that is not in a directly referenced assembly.
// if we don't do anything special for that situation, it will fail to resolve. We should fix this
// in the ILPostProcessing API. As a workaround, we rely on the fact here that the indirect references
// are always located next to direct references, so we search in all directories of direct references we
// got passed, and if we find the file in there, we resolve to it.
foreach (string parentDir in assemblyReferences.Select(Path.GetDirectoryName).Distinct())
{
string candidate = Path.Combine(parentDir, dllName);
if (File.Exists(candidate))
return candidate;
}
return null;
}
// open file as MemoryStream.
// ILPostProcessor is multithreaded.
// retry a few times in case another thread is still accessing the file.
static MemoryStream MemoryStreamFor(string fileName)
{
return Retry(10, TimeSpan.FromSeconds(1), () =>
{
byte[] byteArray;
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
byteArray = new byte[fs.Length];
int readLength = fs.Read(byteArray, 0, (int)fs.Length);
if (readLength != fs.Length)
throw new InvalidOperationException("File read length is not full length of file.");
}
return new MemoryStream(byteArray);
});
}
static MemoryStream Retry(int retryCount, TimeSpan waitTime, Func<MemoryStream> func)
{
try
{
return func();
}
catch (IOException)
{
if (retryCount == 0)
throw;
Console.WriteLine($"Caught IO Exception, trying {retryCount} more times");
Thread.Sleep(waitTime);
return Retry(retryCount - 1, waitTime, func);
}
}
// if the CompiledAssembly's AssemblyDefinition is known, we can add it
public void SetAssemblyDefinitionForCompiledAssembly(AssemblyDefinition assemblyDefinition)
{
selfAssembly = assemblyDefinition;
}
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0b3e94696e22440ead0b3a42411bbe14
timeCreated: 1629693784

View File

@ -0,0 +1,53 @@
// helper function to use ILPostProcessor for an assembly from file.
// we keep this in Weaver folder because we can access CompilationPipleine here.
// in tests folder we can't, unless we rename to "Unity.*.CodeGen",
// but then tests wouldn't be weaved anymore.
#if UNITY_2020_3_OR_NEWER
using System;
using System.IO;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
namespace Mirror.Weaver
{
public static class ILPostProcessorFromFile
{
// read, weave, write file via ILPostProcessor
public static void ILPostProcessFile(string assemblyPath, string[] references, Action<string> OnWarning, Action<string> OnError)
{
// we COULD Weave() with a test logger manually.
// but for test result consistency on all platforms,
// let's invoke the ILPostProcessor here too.
CompiledAssemblyFromFile assembly = new CompiledAssemblyFromFile(assemblyPath);
assembly.References = references;
// create ILPP and check WillProcess like Unity would.
ILPostProcessorHook ilpp = new ILPostProcessorHook();
if (ilpp.WillProcess(assembly))
{
//Debug.Log($"Will Process: {assembly.Name}");
// process it like Unity would
ILPostProcessResult result = ilpp.Process(assembly);
// handle the error messages like Unity would
foreach (DiagnosticMessage message in result.Diagnostics)
{
if (message.DiagnosticType == DiagnosticType.Warning)
{
OnWarning(message.MessageData);
}
else if (message.DiagnosticType == DiagnosticType.Error)
{
OnError(message.MessageData);
}
}
// save the weaved assembly to file.
// some tests open it and check for certain IL code.
File.WriteAllBytes(assemblyPath, result.InMemoryAssembly.PeData);
}
}
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2a4b115486b74d27a9540f3c39ae2d46
timeCreated: 1630152191

View File

@ -0,0 +1,143 @@
// hook via ILPostProcessor from Unity 2020.3+
// (2020.1 has errors https://github.com/vis2k/Mirror/issues/2912)
#if UNITY_2020_3_OR_NEWER
// Unity.CompilationPipeline reference is only resolved if assembly name is
// Unity.*.CodeGen:
// https://forum.unity.com/threads/how-does-unity-do-codegen-and-why-cant-i-do-it-myself.853867/#post-5646937
using System.IO;
using System.Linq;
// to use Mono.CecilX here, we need to 'override references' in the
// Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.
// otherwise we get a reflection exception with 'file not found: CecilX'.
using Mono.CecilX;
using Mono.CecilX.Cil;
using Unity.CompilationPipeline.Common.ILPostProcessing;
// IMPORTANT: 'using UnityEngine' does not work in here.
// Unity gives "(0,0): error System.Security.SecurityException: ECall methods must be packaged into a system module."
//using UnityEngine;
namespace Mirror.Weaver
{
public class ILPostProcessorHook : ILPostProcessor
{
// from CompilationFinishedHook
const string MirrorRuntimeAssemblyName = "Mirror";
// ILPostProcessor is invoked by Unity.
// we can not tell it to ignore certain assemblies before processing.
// add a 'ignore' define for convenience.
// => WeaverTests/WeaverAssembler need it to avoid Unity running it
public const string IgnoreDefine = "ILPP_IGNORE";
// we can't use Debug.Log in ILPP, so we need a custom logger
ILPostProcessorLogger Log = new ILPostProcessorLogger();
// ???
public override ILPostProcessor GetInstance() => this;
// check if assembly has the 'ignore' define
static bool HasDefine(ICompiledAssembly assembly, string define) =>
assembly.Defines != null &&
assembly.Defines.Contains(define);
// process Mirror, or anything that references Mirror
public override bool WillProcess(ICompiledAssembly compiledAssembly)
{
// compiledAssembly.References are file paths:
// Library/Bee/artifacts/200b0aE.dag/Mirror.CompilerSymbols.dll
// Assets/Mirror/Plugins/Mono.Cecil/Mono.CecilX.dll
// /Applications/Unity/Hub/Editor/2021.2.0b6_apple_silicon/Unity.app/Contents/NetStandard/ref/2.1.0/netstandard.dll
//
// log them to see:
// foreach (string reference in compiledAssembly.References)
// LogDiagnostics($"{compiledAssembly.Name} references {reference}");
bool relevant = compiledAssembly.Name == MirrorRuntimeAssemblyName ||
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == MirrorRuntimeAssemblyName);
bool ignore = HasDefine(compiledAssembly, IgnoreDefine);
return relevant && !ignore;
}
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
{
//Log.Warning($"Processing {compiledAssembly.Name}");
// load the InMemoryAssembly peData into a MemoryStream
byte[] peData = compiledAssembly.InMemoryAssembly.PeData;
//LogDiagnostics($" peData.Length={peData.Length} bytes");
using (MemoryStream stream = new MemoryStream(peData))
using (ILPostProcessorAssemblyResolver asmResolver = new ILPostProcessorAssemblyResolver(compiledAssembly, Log))
{
// we need to load symbols. otherwise we get:
// "(0,0): error Mono.CecilX.Cil.SymbolsNotFoundException: No symbol found for file: "
using (MemoryStream symbols = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData))
{
ReaderParameters readerParameters = new ReaderParameters{
SymbolStream = symbols,
ReadWrite = true,
ReadSymbols = true,
AssemblyResolver = asmResolver,
// custom reflection importer to fix System.Private.CoreLib
// not being found in custom assembly resolver above.
ReflectionImporterProvider = new ILPostProcessorReflectionImporterProvider()
};
using (AssemblyDefinition asmDef = AssemblyDefinition.ReadAssembly(stream, readerParameters))
{
// resolving a Mirror.dll type like NetworkServer while
// weaving Mirror.dll does not work. it throws a
// NullReferenceException in WeaverTypes.ctor
// when Resolve() is called on the first Mirror type.
// need to add the AssemblyDefinition itself to use.
asmResolver.SetAssemblyDefinitionForCompiledAssembly(asmDef);
// weave this assembly.
Weaver weaver = new Weaver(Log);
if (weaver.Weave(asmDef, asmResolver, out bool modified))
{
//Log.Warning($"Weaving succeeded for: {compiledAssembly.Name}");
// write if modified
if (modified)
{
// when weaving Mirror.dll with ILPostProcessor,
// Weave() -> WeaverTypes -> resolving the first
// type in Mirror.dll adds a reference to
// Mirror.dll even though we are in Mirror.dll.
// -> this would throw an exception:
// "Mirror references itself" and not compile
// -> need to detect and fix manually here
if (asmDef.MainModule.AssemblyReferences.Any(r => r.Name == asmDef.Name.Name))
{
asmDef.MainModule.AssemblyReferences.Remove(asmDef.MainModule.AssemblyReferences.First(r => r.Name == asmDef.Name.Name));
//Log.Warning($"fixed self referencing Assembly: {asmDef.Name.Name}");
}
MemoryStream peOut = new MemoryStream();
MemoryStream pdbOut = new MemoryStream();
WriterParameters writerParameters = new WriterParameters
{
SymbolWriterProvider = new PortablePdbWriterProvider(),
SymbolStream = pdbOut,
WriteSymbols = true
};
asmDef.Write(peOut, writerParameters);
InMemoryAssembly inMemory = new InMemoryAssembly(peOut.ToArray(), pdbOut.ToArray());
return new ILPostProcessResult(inMemory, Log.Logs);
}
}
// if anything during Weave() fails, we log an error.
// don't need to indicate 'weaving failed' again.
// in fact, this would break tests only expecting certain errors.
//else Log.Error($"Weaving failed for: {compiledAssembly.Name}");
}
}
}
// always return an ILPostProcessResult with Logs.
// otherwise we won't see Logs if weaving failed.
return new ILPostProcessResult(compiledAssembly.InMemoryAssembly, Log.Logs);
}
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5f113eb695b348b5b28cd85358c8959a
timeCreated: 1628859074

View File

@ -0,0 +1,68 @@
using System.Collections.Generic;
using Mono.CecilX;
using Unity.CompilationPipeline.Common.Diagnostics;
namespace Mirror.Weaver
{
public class ILPostProcessorLogger : Logger
{
// can't Debug.Log in ILPostProcessor. need to add to this list.
internal List<DiagnosticMessage> Logs = new List<DiagnosticMessage>();
void Add(string message, DiagnosticType logType)
{
Logs.Add(new DiagnosticMessage
{
// TODO add file etc. for double click opening later?
DiagnosticType = logType, // doesn't have .Log
File = null,
Line = 0,
Column = 0,
MessageData = message
});
}
public void LogDiagnostics(string message, DiagnosticType logType = DiagnosticType.Warning)
{
// TODO IN-44868 FIX IS IN 2021.3.32f1, 2022.3.11f1, 2023.2.0b13 and 2023.3.0a8
// DiagnosticMessage can't display \n for some reason.
// it just cuts it off and we don't see any stack trace.
// so let's replace all line breaks so we get the stack trace.
// (Unity 2021.2.0b6 apple silicon)
//message = message.Replace("\n", "/");
// lets break it into several messages instead so it's easier readable
string[] lines = message.Split('\n');
// if it's just one line, simply log it
if (lines.Length == 1)
{
// tests assume exact message log.
// don't include 'Weaver: ...' or similar.
Add($"{message}", logType);
}
// for multiple lines, log each line separately with start/end indicators
else
{
// first line with Weaver: ... first
Add("----------------------------------------------", logType);
foreach (string line in lines) Add(line, logType);
Add("----------------------------------------------", logType);
}
}
public void Warning(string message) => Warning(message, null);
public void Warning(string message, MemberReference mr)
{
if (mr != null) message = $"{message} (at {mr})";
LogDiagnostics(message, DiagnosticType.Warning);
}
public void Error(string message) => Error(message, null);
public void Error(string message, MemberReference mr)
{
if (mr != null) message = $"{message} (at {mr})";
LogDiagnostics(message, DiagnosticType.Error);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e7b56e7826664e34a415e4b70d958f2a
timeCreated: 1629533154

View File

@ -0,0 +1,36 @@
// based on paul's resolver from
// https://github.com/MirageNet/Mirage/commit/def64cd1db525398738f057b3d1eb1fe8afc540c?branch=def64cd1db525398738f057b3d1eb1fe8afc540c&diff=split
//
// ILPostProcessorAssemblyRESOLVER does not find the .dll file for:
// "System.Private.CoreLib"
// we need this custom reflection importer to fix that.
using System.Linq;
using System.Reflection;
using Mono.CecilX;
namespace Mirror.Weaver
{
internal class ILPostProcessorReflectionImporter : DefaultReflectionImporter
{
const string SystemPrivateCoreLib = "System.Private.CoreLib";
readonly AssemblyNameReference fixedCoreLib;
public ILPostProcessorReflectionImporter(ModuleDefinition module) : base(module)
{
// find the correct library for "System.Private.CoreLib".
// either mscorlib or netstandard.
// defaults to System.Private.CoreLib if not found.
fixedCoreLib = module.AssemblyReferences.FirstOrDefault(a => a.Name == "mscorlib" || a.Name == "netstandard" || a.Name == SystemPrivateCoreLib);
}
public override AssemblyNameReference ImportReference(AssemblyName name)
{
// System.Private.CoreLib?
if (name.Name == SystemPrivateCoreLib && fixedCoreLib != null)
return fixedCoreLib;
// otherwise import as usual
return base.ImportReference(name);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6403a7e3b3ae4e009ae282f111d266e0
timeCreated: 1629709256

View File

@ -0,0 +1,16 @@
// based on paul's resolver from
// https://github.com/MirageNet/Mirage/commit/def64cd1db525398738f057b3d1eb1fe8afc540c?branch=def64cd1db525398738f057b3d1eb1fe8afc540c&diff=split
//
// ILPostProcessorAssemblyRESOLVER does not find the .dll file for:
// "System.Private.CoreLib"
// we need this custom reflection importer to fix that.
using Mono.CecilX;
namespace Mirror.Weaver
{
internal class ILPostProcessorReflectionImporterProvider : IReflectionImporterProvider
{
public IReflectionImporter GetReflectionImporter(ModuleDefinition module) =>
new ILPostProcessorReflectionImporter(module);
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a1003b568bad4e69b961c4c81d5afd96
timeCreated: 1629709223

View File

@ -0,0 +1,359 @@
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 type) =>
type.IsGenericType
? td.GetElementType().FullName == type.FullName
: td.FullName == type.FullName;
// check if 'td' is exactly of type T.
// it does not check if any base type is of <T>, only the specific type.
// for example:
// NetworkConnection Is NetworkConnection: true
// NetworkConnectionToClient Is NetworkConnection: false
public static bool Is<T>(this TypeReference td) => Is(td, typeof(T));
// check if 'tr' is derived from T.
// it does not check if 'tr' is exactly T.
// for example:
// NetworkConnection IsDerivedFrom<NetworkConnection>: false
// NetworkConnectionToClient IsDerivedFrom<NetworkConnection>: true
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) =>
tr is ArrayType arrayType && arrayType.Rank > 1;
// Does type use netId as backing field
public static bool IsNetworkIdentityField(this TypeReference tr) =>
tr.Is<UnityEngine.GameObject>() ||
tr.Is<NetworkIdentity>() ||
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
tr.IsDerivedFrom<NetworkBehaviour>() ||
tr.Is<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;
}
// Makes T => Variable and imports function
public static MethodReference MakeGeneric(this MethodReference generic, ModuleDefinition module, TypeReference variableReference)
{
GenericInstanceMethod instance = new GenericInstanceMethod(generic);
instance.GenericArguments.Add(variableReference);
MethodReference readFunc = module.ImportReference(instance);
return readFunc;
}
// 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
// Note that calling ArraySegment`T.get_Count directly gives an invalid IL error
public static MethodReference MakeHostInstanceGeneric(this MethodReference self, ModuleDefinition module, GenericInstanceType instanceType)
{
MethodReference reference = new MethodReference(self.Name, self.ReturnType, instanceType)
{
CallingConvention = self.CallingConvention,
HasThis = self.HasThis,
ExplicitThis = self.ExplicitThis
};
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 module.ImportReference(reference);
}
// needed for NetworkBehaviour<T> support
// https://github.com/vis2k/Mirror/pull/3073/
public static FieldReference MakeHostInstanceGeneric(this FieldReference self)
{
var declaringType = new GenericInstanceType(self.DeclaringType);
foreach (var parameter in self.DeclaringType.GenericParameters)
{
declaringType.GenericArguments.Add(parameter);
}
return new FieldReference(self.Name, self.FieldType, declaringType);
}
// 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
// Note that calling ArraySegment`T.get_Count directly gives an invalid IL error
public static FieldReference SpecializeField(this FieldReference self, ModuleDefinition module, GenericInstanceType instanceType)
{
FieldReference reference = new FieldReference(self.Name, self.FieldType, instanceType);
return module.ImportReference(reference);
}
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;
}
// Finds public fields in type and base type
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeReference variable)
{
return FindAllPublicFields(variable.Resolve());
}
// Finds public fields in type and base type
public static IEnumerable<FieldDefinition> FindAllPublicFields(this TypeDefinition typeDefinition)
{
while (typeDefinition != null)
{
foreach (FieldDefinition field in typeDefinition.Fields)
{
// ignore static, private, protected fields
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3485
// credit: James Frowen
if (field.IsStatic || field.IsPrivate || field.IsFamily)
continue;
// also ignore internal fields
// we dont want to create different writers for this type if they are in current dll or another dll
// so we have to ignore internal in all cases
if (field.IsAssembly)
continue;
if (field.IsNotSerialized)
continue;
yield return field;
}
try
{
typeDefinition = typeDefinition.BaseType?.Resolve();
}
catch (AssemblyResolutionException)
{
break;
}
}
}
public static bool ContainsClass(this ModuleDefinition module, string nameSpace, string className) =>
module.GetTypes().Any(td => td.Namespace == nameSpace &&
td.Name == className);
public static AssemblyNameReference FindReference(this ModuleDefinition module, string referenceName)
{
foreach (AssemblyNameReference reference in module.AssemblyReferences)
{
if (reference.Name == referenceName)
return reference;
}
return null;
}
// Takes generic arguments from child class and applies them to parent reference, if possible
// eg makes `Base<T>` in Child<int> : Base<int> have `int` instead of `T`
// Originally by James-Frowen under MIT
// https://github.com/MirageNet/Mirage/commit/cf91e1d54796866d2cf87f8e919bb5c681977e45
public static TypeReference ApplyGenericParameters(this TypeReference parentReference,
TypeReference childReference)
{
// If the parent is not generic, we got nothing to apply
if (!parentReference.IsGenericInstance)
return parentReference;
GenericInstanceType parentGeneric = (GenericInstanceType)parentReference;
// make new type so we can replace the args on it
// resolve it so we have non-generic instance (eg just instance with <T> instead of <int>)
// if we don't cecil will make it double generic (eg INVALID IL)
GenericInstanceType generic = new GenericInstanceType(parentReference.Resolve());
foreach (TypeReference arg in parentGeneric.GenericArguments)
generic.GenericArguments.Add(arg);
for (int i = 0; i < generic.GenericArguments.Count; i++)
{
// if arg is not generic
// eg List<int> would be int so not generic.
// But List<T> would be T so is generic
if (!generic.GenericArguments[i].IsGenericParameter)
continue;
// get the generic name, eg T
string name = generic.GenericArguments[i].Name;
// find what type T is, eg turn it into `int` if `List<int>`
TypeReference arg = FindMatchingGenericArgument(childReference, name);
// import just to be safe
TypeReference imported = parentReference.Module.ImportReference(arg);
// set arg on generic, parent ref will be Base<int> instead of just Base<T>
generic.GenericArguments[i] = imported;
}
return generic;
}
// Finds the type reference for a generic parameter with the provided name in the child reference
// Originally by James-Frowen under MIT
// https://github.com/MirageNet/Mirage/commit/cf91e1d54796866d2cf87f8e919bb5c681977e45
static TypeReference FindMatchingGenericArgument(TypeReference childReference, string paramName)
{
TypeDefinition def = childReference.Resolve();
// child class must be generic if we are in this part of the code
// eg Child<T> : Base<T> <--- child must have generic if Base has T
// vs Child : Base<int> <--- wont be here if Base has int (we check if T exists before calling this)
if (!def.HasGenericParameters)
throw new InvalidOperationException(
"Base class had generic parameters, but could not find them in child class");
// go through parameters in child class, and find the generic that matches the name
for (int i = 0; i < def.GenericParameters.Count; i++)
{
GenericParameter param = def.GenericParameters[i];
if (param.Name == paramName)
{
GenericInstanceType generic = (GenericInstanceType)childReference;
// return generic arg with same index
return generic.GenericArguments[i];
}
}
// this should never happen, if it does it means that this code is bugged
throw new InvalidOperationException("Did not find matching generic");
}
}
}

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,26 @@
using System.IO;
using System.Linq;
using System.Reflection;
using Mono.CecilX;
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:\", "");
}
public 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))
);
}
}
}

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,13 @@
using Mono.CecilX;
namespace Mirror.Weaver
{
// not static, because ILPostProcessor is multithreaded
public interface Logger
{
void Warning(string message);
void Warning(string message, MemberReference mr);
void Error(string message);
void Error(string message, MemberReference mr);
}
}

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,8 @@
fileFormatVersion: 2
guid: e538d627280d2471b8c72fdea822ca49
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,130 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
// Processes [Command] methods in NetworkBehaviour
public static class CommandProcessor
{
/*
// generates code like:
public void CmdThrust(float thrusting, int spin)
{
NetworkWriterPooled writer = NetworkWriterPool.Get();
writer.Write(thrusting);
writer.WritePackedUInt32((uint)spin);
base.SendCommandInternal(cmdName, cmdHash, writer, channel);
NetworkWriterPool.Return(writer);
}
public void CallCmdThrust(float thrusting, int spin)
{
// whatever the user was doing before
}
Originally HLAPI put the send message code inside the Call function
and then proceeded to replace every call to CmdTrust with CallCmdTrust
This method moves all the user's code into the "CallCmd" method
and replaces the body of the original method with the send message code.
This way we do not need to modify the code anywhere else, and this works
correctly in dependent assemblies
*/
public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute commandAttr, ref bool WeavingFailed)
{
MethodDefinition cmd = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
ILProcessor worker = md.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
// NetworkWriter writer = new NetworkWriter();
NetworkBehaviourProcessor.WriteGetWriter(worker, weaverTypes);
// write all the arguments that the user passed to the Cmd call
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.Command, ref WeavingFailed))
return null;
int channel = commandAttr.GetField("channel", 0);
bool requiresAuthority = commandAttr.GetField("requiresAuthority", true);
// invoke internal send and return
// load 'base.' to call the SendCommand function with
worker.Emit(OpCodes.Ldarg_0);
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
worker.Emit(OpCodes.Ldstr, md.FullName);
// pass the function hash so we don't have to compute it at runtime
// otherwise each GetStableHash call requires O(N) complexity.
// noticeable for long function names:
// https://github.com/MirrorNetworking/Mirror/issues/3375
worker.Emit(OpCodes.Ldc_I4, md.FullName.GetStableHashCode());
// writer
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, channel);
// requiresAuthority ? 1 : 0
worker.Emit(requiresAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
worker.Emit(OpCodes.Call, weaverTypes.sendCommandInternal);
NetworkBehaviourProcessor.WriteReturnWriter(worker, weaverTypes);
worker.Emit(OpCodes.Ret);
return cmd;
}
/*
// generates code like:
protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader, NetworkConnection senderConnection)
{
if (!NetworkServer.active)
{
return;
}
((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32());
}
*/
public static MethodDefinition ProcessCommandInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition method, MethodDefinition cmdCallFunc, ref bool WeavingFailed)
{
string cmdName = Weaver.GenerateMethodName(RemoteCalls.RemoteProcedureCalls.InvokeRpcPrefix, method);
MethodDefinition cmd = new MethodDefinition(cmdName,
MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
weaverTypes.Import(typeof(void)));
ILProcessor worker = cmd.Body.GetILProcessor();
Instruction label = worker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteServerActiveCheck(worker, weaverTypes, method.Name, label, "Command");
// setup for reader
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Castclass, td);
if (!NetworkBehaviourProcessor.ReadArguments(method, readers, Log, worker, RemoteCallType.Command, ref WeavingFailed))
return null;
AddSenderConnection(method, worker);
// invoke actual command function
worker.Emit(OpCodes.Callvirt, cmdCallFunc);
worker.Emit(OpCodes.Ret);
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, cmd.Parameters);
td.Methods.Add(cmd);
return cmd;
}
static void AddSenderConnection(MethodDefinition method, ILProcessor worker)
{
foreach (ParameterDefinition param in method.Parameters)
{
if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command))
{
// NetworkConnection is 3nd arg (arg0 is "obj" not "this" because method is static)
// example: static void InvokeCmdCmdSendCommand(NetworkBehaviour obj, NetworkReader reader, NetworkConnection connection)
worker.Emit(OpCodes.Ldarg_2);
}
}
}
}
}

View File

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

View File

@ -0,0 +1,139 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class MethodProcessor
{
const string RpcPrefix = "UserCode_";
// For a function like
// [ClientRpc] void RpcTest(int value),
// Weaver substitutes the method and moves the code to a new method:
// UserCode_RpcTest(int value) <- contains original code
// RpcTest(int value) <- serializes parameters, sends the message
//
// Note that all the calls to the method remain untouched.
// FixRemoteCallToBaseMethod replaces them afterwards.
public static MethodDefinition SubstituteMethod(Logger Log, TypeDefinition td, MethodDefinition md, ref bool WeavingFailed)
{
string newName = Weaver.GenerateMethodName(RpcPrefix, md);
MethodDefinition cmd = new MethodDefinition(newName, md.Attributes, md.ReturnType);
// force the substitute method to be protected.
// -> public would show in the Inspector for UnityEvents as
// User_CmdUsePotion() etc. but the user shouldn't use those.
// -> private would not allow inheriting classes to call it, see
// OverrideVirtualWithBaseCallsBothVirtualAndBase test.
// -> IL has no concept of 'protected', it's called IsFamily there.
cmd.IsPublic = false;
cmd.IsFamily = true;
// add parameters
foreach (ParameterDefinition pd in md.Parameters)
{
cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
}
// swap bodies
(cmd.Body, md.Body) = (md.Body, cmd.Body);
// Move over all the debugging information
foreach (SequencePoint sequencePoint in md.DebugInformation.SequencePoints)
cmd.DebugInformation.SequencePoints.Add(sequencePoint);
md.DebugInformation.SequencePoints.Clear();
foreach (CustomDebugInformation customInfo in md.CustomDebugInformations)
cmd.CustomDebugInformations.Add(customInfo);
md.CustomDebugInformations.Clear();
(md.DebugInformation.Scope, cmd.DebugInformation.Scope) = (cmd.DebugInformation.Scope, md.DebugInformation.Scope);
td.Methods.Add(cmd);
FixRemoteCallToBaseMethod(Log, td, cmd, ref WeavingFailed);
return cmd;
}
// For a function like
// [ClientRpc] void RpcTest(int value),
// Weaver substitutes the method and moves the code to a new method:
// UserCode_RpcTest(int value) <- contains original code
// RpcTest(int value) <- serializes parameters, sends the message
//
// FixRemoteCallToBaseMethod replaces all calls to
// RpcTest(value)
// with
// UserCode_RpcTest(value)
public static void FixRemoteCallToBaseMethod(Logger Log, TypeDefinition type, MethodDefinition method, ref bool WeavingFailed)
{
string callName = method.Name;
// Cmd/rpc start with Weaver.RpcPrefix
// e.g. CallCmdDoSomething
if (!callName.StartsWith(RpcPrefix))
return;
// e.g. CmdDoSomething
string baseRemoteCallName = method.Name.Substring(RpcPrefix.Length);
foreach (Instruction instruction in method.Body.Instructions)
{
// is this instruction a Call to a method?
// if yes, output the method so we can check it.
if (IsCallToMethod(instruction, out MethodDefinition calledMethod))
{
// when considering if 'calledMethod' is a call to 'method',
// we originally compared .Name.
//
// to fix IL2CPP build bugs with overloaded Rpcs, we need to
// generated rpc names like
// RpcTest(string value) => RpcTestString(strig value)
// RpcTest(int value) => RpcTestInt(int value)
// to make them unique.
//
// calledMethod.Name is still "RpcTest", so we need to
// convert this to the generated name as well before comparing.
string calledMethodName_Generated = Weaver.GenerateMethodName("", calledMethod);
if (calledMethodName_Generated == baseRemoteCallName)
{
TypeDefinition baseType = type.BaseType.Resolve();
MethodDefinition baseMethod = baseType.GetMethodInBaseType(callName);
if (baseMethod == null)
{
Log.Error($"Could not find base method for {callName}", method);
WeavingFailed = true;
return;
}
if (!baseMethod.IsVirtual)
{
Log.Error($"Could not find base method that was virtual {callName}", method);
WeavingFailed = true;
return;
}
instruction.Operand = baseMethod;
}
}
}
}
static bool IsCallToMethod(Instruction instruction, out MethodDefinition calledMethod)
{
if (instruction.OpCode == OpCodes.Call &&
instruction.Operand is MethodDefinition method)
{
calledMethod = method;
return true;
}
else
{
calledMethod = null;
return false;
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,104 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
// Processes [Rpc] methods in NetworkBehaviour
public static class RpcProcessor
{
public static MethodDefinition ProcessRpcInvoke(WeaverTypes weaverTypes, Writers writers, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
{
string rpcName = Weaver.GenerateMethodName(RemoteCalls.RemoteProcedureCalls.InvokeRpcPrefix, md);
MethodDefinition rpc = new MethodDefinition(rpcName, MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
weaverTypes.Import(typeof(void)));
ILProcessor worker = rpc.Body.GetILProcessor();
Instruction label = worker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "RPC");
// setup for reader
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Castclass, td);
if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.ClientRpc, ref WeavingFailed))
return null;
// invoke actual command function
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
worker.Emit(OpCodes.Ret);
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters);
td.Methods.Add(rpc);
return rpc;
}
/*
* generates code like:
public void RpcTest (int param)
{
NetworkWriter writer = new NetworkWriter ();
writer.WritePackedUInt32((uint)param);
base.SendRPCInternal(typeof(class),"RpcTest", writer, 0);
}
public void CallRpcTest (int param)
{
// whatever the user did before
}
Originally HLAPI put the send message code inside the Call function
and then proceeded to replace every call to RpcTest with CallRpcTest
This method moves all the user's code into the "CallRpc" method
and replaces the body of the original method with the send message code.
This way we do not need to modify the code anywhere else, and this works
correctly in dependent assemblies
*/
public static MethodDefinition ProcessRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute clientRpcAttr, ref bool WeavingFailed)
{
MethodDefinition rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
ILProcessor worker = md.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
// add a log message if needed for debugging
//worker.Emit(OpCodes.Ldstr, $"Call ClientRpc function {md.Name}");
//worker.Emit(OpCodes.Call, WeaverTypes.logErrorReference);
NetworkBehaviourProcessor.WriteGetWriter(worker, weaverTypes);
// write all the arguments that the user passed to the Rpc call
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.ClientRpc, ref WeavingFailed))
return null;
int channel = clientRpcAttr.GetField("channel", 0);
bool includeOwner = clientRpcAttr.GetField("includeOwner", true);
// invoke SendInternal and return
// this
worker.Emit(OpCodes.Ldarg_0);
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
worker.Emit(OpCodes.Ldstr, md.FullName);
// pass the function hash so we don't have to compute it at runtime
// otherwise each GetStableHash call requires O(N) complexity.
// noticeable for long function names:
// https://github.com/MirrorNetworking/Mirror/issues/3375
worker.Emit(OpCodes.Ldc_I4, md.FullName.GetStableHashCode());
// writer
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, channel);
// includeOwner ? 1 : 0
worker.Emit(includeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
worker.Emit(OpCodes.Callvirt, weaverTypes.sendRpcInternal);
NetworkBehaviourProcessor.WriteReturnWriter(worker, weaverTypes);
worker.Emit(OpCodes.Ret);
return rpc;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,206 @@
// [SyncVar] int health;
// is replaced with:
// public int Networkhealth { get; set; } properties.
// this class processes all access to 'health' and replaces it with 'Networkhealth'
using System;
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
public static class SyncVarAttributeAccessReplacer
{
// process the module
public static void Process(Logger Log, ModuleDefinition moduleDef, SyncVarAccessLists syncVarAccessLists)
{
DateTime startTime = DateTime.Now;
// process all classes in this module
foreach (TypeDefinition td in moduleDef.Types)
{
if (td.IsClass)
{
ProcessClass(Log, syncVarAccessLists, td);
}
}
Console.WriteLine($" ProcessSitesModule {moduleDef.Name} elapsed time:{(DateTime.Now - startTime)}");
}
static void ProcessClass(Logger Log, SyncVarAccessLists syncVarAccessLists, TypeDefinition td)
{
//Console.WriteLine($" ProcessClass {td}");
// process all methods in this class
foreach (MethodDefinition md in td.Methods)
{
ProcessMethod(Log, syncVarAccessLists, md);
}
// processes all nested classes in this class recursively
foreach (TypeDefinition nested in td.NestedTypes)
{
ProcessClass(Log, syncVarAccessLists, nested);
}
}
static void ProcessMethod(Logger Log, SyncVarAccessLists syncVarAccessLists, MethodDefinition md)
{
// process all references to replaced members with properties
//Log.Warning($" ProcessSiteMethod {md}");
// skip static constructor, "MirrorProcessed", "InvokeUserCode_"
if (md.Name == ".cctor" ||
md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
md.Name.StartsWith(RemoteCalls.RemoteProcedureCalls.InvokeRpcPrefix))
return;
// skip abstract
if (md.IsAbstract)
{
return;
}
// go through all instructions of this method
if (md.Body != null && md.Body.Instructions != null)
{
for (int i = 0; i < md.Body.Instructions.Count;)
{
Instruction instr = md.Body.Instructions[i];
i += ProcessInstruction(Log, syncVarAccessLists, md, instr, i);
}
}
}
static int ProcessInstruction(Logger Log, SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, int iCount)
{
// stfld (sets value of a field)?
if (instr.OpCode == OpCodes.Stfld)
{
// operand is a FieldDefinition in the same assembly?
if (instr.Operand is FieldDefinition opFieldst)
{
ProcessSetInstruction(syncVarAccessLists, md, instr, opFieldst);
}
// operand is a FieldReference in another assembly?
// this is not supported just yet.
// compilation error is better than silently failing SyncVar serialization at runtime.
// https://github.com/MirrorNetworking/Mirror/issues/3525
else if (instr.Operand is FieldReference opFieldstRef)
{
// resolve it from the other assembly
FieldDefinition field = opFieldstRef.Resolve();
// [SyncVar]?
if (field.HasCustomAttribute<SyncVarAttribute>())
{
// ILPostProcessor would need to Process() the assembly's
// references before processing this one.
// we can not control the order.
// instead, Log an error to suggest adding a SetSyncVar(value) function.
// this is a very easy solution for a very rare edge case.
Log.Error($"'[SyncVar] {opFieldstRef.DeclaringType.Name}.{opFieldstRef.Name}' in '{field.Module.Name}' is modified by '{md.FullName}' in '{md.Module.Name}'. Modifying a [SyncVar] from another assembly is not supported. Please add a: 'public void Set{opFieldstRef.Name}(value) {{ this.{opFieldstRef.Name} = value; }}' method in '{opFieldstRef.DeclaringType.Name}' and call this function from '{md.FullName}' instead.");
}
}
}
// ldfld (load value of a field)?
if (instr.OpCode == OpCodes.Ldfld)
{
// operand is a FieldDefinition in the same assembly?
if (instr.Operand is FieldDefinition opFieldld)
{
// this instruction gets the value of a field. cache the field reference.
ProcessGetInstruction(syncVarAccessLists, md, instr, opFieldld);
}
}
// ldflda (load field address aka reference)
if (instr.OpCode == OpCodes.Ldflda)
{
// operand is a FieldDefinition in the same assembly?
if (instr.Operand is FieldDefinition opFieldlda)
{
// watch out for initobj instruction
// see https://github.com/vis2k/Mirror/issues/696
return ProcessLoadAddressInstruction(syncVarAccessLists, md, instr, opFieldlda, iCount);
}
}
// we processed one instruction (instr)
return 1;
}
// replaces syncvar write access with the NetworkXYZ.set property calls
static void ProcessSetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
{
// don't replace property call sites in constructors
if (md.Name == ".ctor")
return;
// does it set a field that we replaced?
if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{
//replace with property
//Log.Warning($" replacing {md.Name}:{i}", opField);
i.OpCode = OpCodes.Call;
i.Operand = replacement;
//Log.Warning($" replaced {md.Name}:{i}", opField);
}
}
// replaces syncvar read access with the NetworkXYZ.get property calls
static void ProcessGetInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction i, FieldDefinition opField)
{
// don't replace property call sites in constructors
if (md.Name == ".ctor")
return;
// does it set a field that we replaced?
if (syncVarAccessLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{
//replace with property
//Log.Warning($" replacing {md.Name}:{i}");
i.OpCode = OpCodes.Call;
i.Operand = replacement;
//Log.Warning($" replaced {md.Name}:{i}");
}
}
static int ProcessLoadAddressInstruction(SyncVarAccessLists syncVarAccessLists, MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
{
// don't replace property call sites in constructors
if (md.Name == ".ctor")
return 1;
// does it set a field that we replaced?
if (syncVarAccessLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
{
// we have a replacement for this property
// is the next instruction a initobj?
Instruction nextInstr = md.Body.Instructions[iCount + 1];
if (nextInstr.OpCode == OpCodes.Initobj)
{
// we need to replace this code with:
// var tmp = new MyStruct();
// this.set_Networkxxxx(tmp);
ILProcessor worker = md.Body.GetILProcessor();
VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType);
md.Body.Variables.Add(tmpVariable);
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
worker.Remove(instr);
worker.Remove(nextInstr);
return 4;
}
}
return 1;
}
}
}

View File

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

View File

@ -0,0 +1,515 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;
namespace Mirror.Weaver
{
// Processes [SyncVar] attribute fields in NetworkBehaviour
// not static, because ILPostProcessor is multithreaded
public class SyncVarAttributeProcessor
{
// ulong = 64 bytes
const int SyncVarLimit = 64;
AssemblyDefinition assembly;
WeaverTypes weaverTypes;
SyncVarAccessLists syncVarAccessLists;
Logger Log;
string HookParameterMessage(string hookName, TypeReference ValueType) =>
$"void {hookName}({ValueType} oldValue, {ValueType} newValue)";
public SyncVarAttributeProcessor(AssemblyDefinition assembly, WeaverTypes weaverTypes, SyncVarAccessLists syncVarAccessLists, Logger Log)
{
this.assembly = assembly;
this.weaverTypes = weaverTypes;
this.syncVarAccessLists = syncVarAccessLists;
this.Log = Log;
}
// Get hook method if any
public MethodDefinition GetHookMethod(TypeDefinition td, FieldDefinition syncVar, ref bool WeavingFailed)
{
CustomAttribute syncVarAttr = syncVar.GetCustomAttribute<SyncVarAttribute>();
if (syncVarAttr == null)
return null;
string hookFunctionName = syncVarAttr.GetField<string>("hook", null);
if (hookFunctionName == null)
return null;
return FindHookMethod(td, syncVar, hookFunctionName, ref WeavingFailed);
}
// Create a field definition for a field that will store the Action<T, T> delegate instance for the syncvar hook method (only instantiate delegate once)
public FieldDefinition CreateNewActionFieldDefinitionFromHookMethod(FieldDefinition syncVarField)
{
TypeReference actionRef = assembly.MainModule.ImportReference(typeof(Action<,>));
GenericInstanceType syncVarHookActionDelegateType = actionRef.MakeGenericInstanceType(syncVarField.FieldType, syncVarField.FieldType);
string syncVarHookDelegateFieldName = $"_Mirror_SyncVarHookDelegate_{syncVarField.Name}";
return new FieldDefinition(syncVarHookDelegateFieldName, FieldAttributes.Public, syncVarHookActionDelegateType);
}
// push hook from GetHookMethod() onto the stack as a new Action<T,T>.
// allows for reuse without handling static/virtual cases every time.
// perf warning: it is recommended to use this method only when generating IL to create a new Action<T, T>() in order to store it into a field
// avoid using this to emit IL to instantiate a new action instance every single time one is needed for the same method
public void GenerateNewActionFromHookMethod(FieldDefinition syncVar, ILProcessor worker, MethodDefinition hookMethod)
{
// IL_000a: ldarg.0
// IL_000b: ldftn instance void Mirror.Examples.Tanks.Tank::ExampleHook(int32, int32)
// IL_0011: newobj instance void class [netstandard]System.Action`2<int32, int32>::.ctor(object, native int)
// we support static hooks and instance hooks.
if (hookMethod.IsStatic)
{
// for static hooks, we need to push 'null' first.
// we can't just push nothing.
// stack would get out of balance because we already pushed
// other stuff above.
worker.Emit(OpCodes.Ldnull);
}
else
{
// for instance hooks, we need to push 'this.' first.
worker.Emit(OpCodes.Ldarg_0);
}
MethodReference hookMethodReference;
// if the network behaviour class is generic, we need to make the method reference generic for correct IL
if (hookMethod.DeclaringType.HasGenericParameters)
{
hookMethodReference = hookMethod.MakeHostInstanceGeneric(hookMethod.Module, hookMethod.DeclaringType.MakeGenericInstanceType(hookMethod.DeclaringType.GenericParameters.ToArray()));
}
else
{
hookMethodReference = hookMethod;
}
// we support regular and virtual hook functions.
if (hookMethod.IsVirtual)
{
// for virtual / overwritten hooks, we need different IL.
// this is from simply testing Action = VirtualHook; in C#.
worker.Emit(OpCodes.Dup);
worker.Emit(OpCodes.Ldvirtftn, hookMethodReference);
}
else
{
worker.Emit(OpCodes.Ldftn, hookMethodReference);
}
// call 'new Action<T,T>()' constructor to convert the function to an action
// we need to make an instance of the generic Action<T,T>.
TypeReference actionRef = assembly.MainModule.ImportReference(typeof(Action<,>));
GenericInstanceType genericInstance = actionRef.MakeGenericInstanceType(syncVar.FieldType, syncVar.FieldType);
worker.Emit(OpCodes.Newobj, weaverTypes.ActionT_T.MakeHostInstanceGeneric(assembly.MainModule, genericInstance));
}
// generates CIL to set an Action<T,T> instance field to a new Action<T,T>(hookMethod)
// this.hookDelegate = new Action<T, T>(HookMethod);
public void GenerateSyncVarHookDelegateInitializer(ILProcessor worker, FieldDefinition syncVar, FieldDefinition hookDelegate, MethodDefinition hookMethod)
{
// push this
worker.Emit(OpCodes.Ldarg_0);
// push new Action<T, T>(hookMethod)
GenerateNewActionFromHookMethod(syncVar, worker, hookMethod);
// set field
worker.Emit(OpCodes.Stfld, hookDelegate);
}
MethodDefinition FindHookMethod(TypeDefinition td, FieldDefinition syncVar, string hookFunctionName, ref bool WeavingFailed)
{
List<MethodDefinition> methods = td.GetMethods(hookFunctionName);
List<MethodDefinition> methodsWith2Param = new List<MethodDefinition>(methods.Where(m => m.Parameters.Count == 2));
if (methodsWith2Param.Count == 0)
{
Log.Error($"Could not find hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
syncVar);
WeavingFailed = true;
return null;
}
foreach (MethodDefinition method in methodsWith2Param)
{
if (MatchesParameters(syncVar, method))
{
return method;
}
}
Log.Error($"Wrong type for Parameter in hook for '{syncVar.Name}', hook name '{hookFunctionName}'. " +
$"Method signature should be {HookParameterMessage(hookFunctionName, syncVar.FieldType)}",
syncVar);
WeavingFailed = true;
return null;
}
bool MatchesParameters(FieldDefinition syncVar, MethodDefinition method)
{
// matches void onValueChange(T oldValue, T newValue)
return method.Parameters[0].ParameterType.FullName == syncVar.FieldType.FullName &&
method.Parameters[1].ParameterType.FullName == syncVar.FieldType.FullName;
}
public MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string originalName, FieldDefinition netFieldId)
{
//Create the get method
MethodDefinition get = new MethodDefinition(
$"get_Network{originalName}", MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
fd.FieldType);
ILProcessor worker = get.Body.GetILProcessor();
FieldReference fr;
if (fd.DeclaringType.HasGenericParameters)
{
fr = fd.MakeHostInstanceGeneric();
}
else
{
fr = fd;
}
FieldReference netIdFieldReference = null;
if (netFieldId != null)
{
if (netFieldId.DeclaringType.HasGenericParameters)
{
netIdFieldReference = netFieldId.MakeHostInstanceGeneric();
}
else
{
netIdFieldReference = netFieldId;
}
}
// [SyncVar] GameObject?
if (fd.FieldType.Is<UnityEngine.GameObject>())
{
// return this.GetSyncVarGameObject(ref field, uint netId);
// this.
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, netIdFieldReference);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, fr);
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarGameObjectReference);
worker.Emit(OpCodes.Ret);
}
// [SyncVar] NetworkIdentity?
else if (fd.FieldType.Is<NetworkIdentity>())
{
// return this.GetSyncVarNetworkIdentity(ref field, uint netId);
// this.
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, netIdFieldReference);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, fr);
worker.Emit(OpCodes.Call, weaverTypes.getSyncVarNetworkIdentityReference);
worker.Emit(OpCodes.Ret);
}
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>() || fd.FieldType.Is<NetworkBehaviour>())
{
// return this.GetSyncVarNetworkBehaviour<T>(ref field, uint netId);
// this.
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, netIdFieldReference);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, fr);
MethodReference getFunc = weaverTypes.getSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType);
worker.Emit(OpCodes.Call, getFunc);
worker.Emit(OpCodes.Ret);
}
// [SyncVar] int, string, etc.
else
{
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, fr);
worker.Emit(OpCodes.Ret);
}
get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
get.Body.InitLocals = true;
get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
return get;
}
// for [SyncVar] health, weaver generates
//
// NetworkHealth
// {
// get => health;
// set => GeneratedSyncVarSetter(...)
// }
//
// the setter used to be manually IL generated, but we moved it to C# :)
public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId, Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)> syncVarHookDelegates, ref bool WeavingFailed)
{
//Create the set method
MethodDefinition set = new MethodDefinition($"set_Network{originalName}", MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
weaverTypes.Import(typeof(void)));
ILProcessor worker = set.Body.GetILProcessor();
FieldReference fr;
if (fd.DeclaringType.HasGenericParameters)
{
fr = fd.MakeHostInstanceGeneric();
}
else
{
fr = fd;
}
FieldReference netIdFieldReference = null;
if (netFieldId != null)
{
if (netFieldId.DeclaringType.HasGenericParameters)
{
netIdFieldReference = netFieldId.MakeHostInstanceGeneric();
}
else
{
netIdFieldReference = netFieldId;
}
}
// if (!SyncVarEqual(value, ref playerData))
Instruction endOfMethod = worker.Create(OpCodes.Nop);
// NOTE: SyncVar...Equal functions are static.
// don't Emit Ldarg_0 aka 'this'.
// call WeaverSyncVarSetter<T>(T value, ref T field, ulong dirtyBit, Action<T, T> OnChanged = null)
// IL_0000: ldarg.0
// IL_0001: ldarg.1
// IL_0002: ldarg.0
// IL_0003: ldflda int32 Mirror.Examples.Tanks.Tank::health
// IL_0008: ldc.i4.1
// IL_0009: conv.i8
// IL_000a: ldnull
// IL_000b: call instance void [Mirror]Mirror.NetworkBehaviour::GeneratedSyncVarSetter<int32>(!!0, !!0&, uint64, class [netstandard]System.Action`2<!!0, !!0>)
// IL_0010: ret
// 'this.' for the call
worker.Emit(OpCodes.Ldarg_0);
// first push 'value'
worker.Emit(OpCodes.Ldarg_1);
// push 'ref T this.field'
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, fr);
// push the dirty bit for this SyncVar
worker.Emit(OpCodes.Ldc_I8, dirtyBit);
// hook? then push 'this.HookDelegate' onto stack
MethodDefinition hookMethod = GetHookMethod(td, fd, ref WeavingFailed);
if (hookMethod != null)
{
// Create the field that will store a single instance of the hook as a delegate (field will be set in constructor)
FieldDefinition hookActionDelegateField = CreateNewActionFieldDefinitionFromHookMethod(fd);
syncVarHookDelegates[fd] = (hookActionDelegateField, hookMethod);
// push this.hookActionDelegateField
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldfld, hookActionDelegateField);
}
// otherwise push 'null' as hook
else
{
worker.Emit(OpCodes.Ldnull);
}
// call GeneratedSyncVarSetter<T>.
// special cases for GameObject/NetworkIdentity/NetworkBehaviour
// passing netId too for persistence.
if (fd.FieldType.Is<UnityEngine.GameObject>())
{
// GameObject setter needs one more parameter: netId field ref
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netIdFieldReference);
worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarSetter_GameObject);
}
else if (fd.FieldType.Is<NetworkIdentity>())
{
// NetworkIdentity setter needs one more parameter: netId field ref
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netIdFieldReference);
worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarSetter_NetworkIdentity);
}
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
else if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>() || fd.FieldType.Is<NetworkBehaviour>())
{
// NetworkIdentity setter needs one more parameter: netId field ref
// (actually its a NetworkBehaviourSyncVar type)
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldflda, netIdFieldReference);
// make generic version of GeneratedSyncVarSetter_NetworkBehaviour<T>
MethodReference getFunc = weaverTypes.generatedSyncVarSetter_NetworkBehaviour_T.MakeGeneric(assembly.MainModule, fd.FieldType);
worker.Emit(OpCodes.Call, getFunc);
}
else
{
// make generic version of GeneratedSyncVarSetter<T>
MethodReference generic = weaverTypes.generatedSyncVarSetter.MakeGeneric(assembly.MainModule, fd.FieldType);
worker.Emit(OpCodes.Call, generic);
}
worker.Append(endOfMethod);
worker.Emit(OpCodes.Ret);
set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType));
set.SemanticsAttributes = MethodSemanticsAttributes.Setter;
return set;
}
public void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)> syncVarHookDelegates, long dirtyBit, ref bool WeavingFailed)
{
string originalName = fd.Name;
// GameObject/NetworkIdentity SyncVars have a new field for netId
FieldDefinition netIdField = null;
// NetworkBehaviour has different field type than other NetworkIdentityFields
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
if (fd.FieldType.IsDerivedFrom<NetworkBehaviour>() || fd.FieldType.Is<NetworkBehaviour>())
{
netIdField = new FieldDefinition($"___{fd.Name}NetId",
FieldAttributes.Family, // needs to be protected for generic classes, otherwise access isn't allowed
weaverTypes.Import<NetworkBehaviourSyncVar>());
netIdField.DeclaringType = td;
syncVarNetIds[fd] = netIdField;
}
else if (fd.FieldType.IsNetworkIdentityField())
{
netIdField = new FieldDefinition($"___{fd.Name}NetId",
FieldAttributes.Family, // needs to be protected for generic classes, otherwise access isn't allowed
weaverTypes.Import<uint>());
netIdField.DeclaringType = td;
syncVarNetIds[fd] = netIdField;
}
MethodDefinition get = GenerateSyncVarGetter(fd, originalName, netIdField);
MethodDefinition set = GenerateSyncVarSetter(td, fd, originalName, dirtyBit, netIdField, syncVarHookDelegates, ref WeavingFailed);
//NOTE: is property even needed? Could just use a setter function?
//create the property
PropertyDefinition propertyDefinition = new PropertyDefinition($"Network{originalName}", PropertyAttributes.None, fd.FieldType)
{
GetMethod = get,
SetMethod = set
};
//add the methods and property to the type.
td.Methods.Add(get);
td.Methods.Add(set);
td.Properties.Add(propertyDefinition);
syncVarAccessLists.replacementSetterProperties[fd] = set;
// replace getter field if GameObject/NetworkIdentity so it uses
// netId instead
// -> only for GameObjects, otherwise an int syncvar's getter would
// end up in recursion.
if (fd.FieldType.IsNetworkIdentityField())
{
syncVarAccessLists.replacementGetterProperties[fd] = get;
}
}
public (List<FieldDefinition> syncVars, Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds, Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)> syncVarHookDelegates) ProcessSyncVars(TypeDefinition td, ref bool WeavingFailed)
{
List<FieldDefinition> syncVars = new List<FieldDefinition>();
Dictionary<FieldDefinition, FieldDefinition> syncVarNetIds = new Dictionary<FieldDefinition, FieldDefinition>();
Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)> syncVarHookDelegates = new Dictionary<FieldDefinition, (FieldDefinition hookDelegateField, MethodDefinition hookMethod)>();
// the mapping of dirtybits to sync-vars is implicit in the order of the fields here. this order is recorded in m_replacementProperties.
// start assigning syncvars at the place the base class stopped, if any
int dirtyBitCounter = syncVarAccessLists.GetSyncVarStart(td.BaseType.FullName);
// find syncvars
foreach (FieldDefinition fd in td.Fields)
{
if (fd.HasCustomAttribute<SyncVarAttribute>())
{
if ((fd.Attributes & FieldAttributes.Static) != 0)
{
Log.Error($"{fd.Name} cannot be static", fd);
WeavingFailed = true;
continue;
}
if (fd.FieldType.IsGenericParameter)
{
Log.Error($"{fd.Name} has generic type. Generic SyncVars are not supported", fd);
WeavingFailed = true;
continue;
}
if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
{
Log.Warning($"{fd.Name} has [SyncVar] attribute. SyncLists should not be marked with SyncVar", fd);
}
else
{
syncVars.Add(fd);
ProcessSyncVar(td, fd, syncVarNetIds, syncVarHookDelegates, 1L << dirtyBitCounter, ref WeavingFailed);
dirtyBitCounter += 1;
if (dirtyBitCounter > SyncVarLimit)
{
Log.Error($"{td.Name} has > {SyncVarLimit} SyncVars. Consider refactoring your class into multiple components", td);
WeavingFailed = true;
continue;
}
}
}
}
// add all the new SyncVar __netId fields
foreach (FieldDefinition fd in syncVarNetIds.Values)
{
td.Fields.Add(fd);
}
// add all of the new SyncVar Action<T,T> fields
foreach((FieldDefinition hookDelegateInstanceField, MethodDefinition) entry in syncVarHookDelegates.Values)
{
td.Fields.Add(entry.hookDelegateInstanceField);
}
// include parent class syncvars
// fixes: https://github.com/MirrorNetworking/Mirror/issues/3457
int parentSyncVarCount = syncVarAccessLists.GetSyncVarStart(td.BaseType.FullName);
syncVarAccessLists.SetNumSyncVars(td.FullName, parentSyncVarCount + syncVars.Count);
return (syncVars, syncVarNetIds, syncVarHookDelegates);
}
}
}

View File

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

View File

@ -0,0 +1,159 @@
using Mono.CecilX;
using Mono.CecilX.Cil;
namespace Mirror.Weaver
{
// Processes [TargetRpc] methods in NetworkBehaviour
public static class TargetRpcProcessor
{
// helper functions to check if the method has a NetworkConnection parameter
public static bool HasNetworkConnectionParameter(MethodDefinition md)
{
if (md.Parameters.Count > 0)
{
// we need to allow both NetworkConnection, and inheriting types.
// NetworkBehaviour.SendTargetRpc takes a NetworkConnection parameter.
// fixes https://github.com/vis2k/Mirror/issues/3290
TypeReference type = md.Parameters[0].ParameterType;
return type.Is<NetworkConnection>() ||
type.IsDerivedFrom<NetworkConnection>();
}
return false;
}
public static MethodDefinition ProcessTargetRpcInvoke(WeaverTypes weaverTypes, Readers readers, Logger Log, TypeDefinition td, MethodDefinition md, MethodDefinition rpcCallFunc, ref bool WeavingFailed)
{
string trgName = Weaver.GenerateMethodName(RemoteCalls.RemoteProcedureCalls.InvokeRpcPrefix, md);
MethodDefinition rpc = new MethodDefinition(trgName, MethodAttributes.Family |
MethodAttributes.Static |
MethodAttributes.HideBySig,
weaverTypes.Import(typeof(void)));
ILProcessor worker = rpc.Body.GetILProcessor();
Instruction label = worker.Create(OpCodes.Nop);
NetworkBehaviourProcessor.WriteClientActiveCheck(worker, weaverTypes, md.Name, label, "TargetRPC");
// setup for reader
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Castclass, td);
// NetworkConnection parameter is optional
if (HasNetworkConnectionParameter(md))
{
// TargetRpcs are sent from server to client.
// on server, we currently support two types:
// TargetRpc(NetworkConnection)
// TargetRpc(NetworkConnectionToClient)
// however, it's always a connection to client.
// in the future, only NetworkConnectionToClient will be supported.
// explicit typing helps catch issues at compile time.
//
// on client, InvokeTargetRpc calls the original code.
// we need to fill in the NetworkConnection parameter.
// NetworkClient.connection is always a connection to server.
//
// we used to pass NetworkClient.connection as the TargetRpc parameter.
// which caused: https://github.com/MirrorNetworking/Mirror/issues/3455
// when the parameter is defined as a NetworkConnectionToClient.
//
// a client's connection never fits into a NetworkConnectionToClient.
// we need to always pass null here.
worker.Emit(OpCodes.Ldnull);
}
// process reader parameters and skip first one if first one is NetworkConnection
if (!NetworkBehaviourProcessor.ReadArguments(md, readers, Log, worker, RemoteCallType.TargetRpc, ref WeavingFailed))
return null;
// invoke actual command function
worker.Emit(OpCodes.Callvirt, rpcCallFunc);
worker.Emit(OpCodes.Ret);
NetworkBehaviourProcessor.AddInvokeParameters(weaverTypes, rpc.Parameters);
td.Methods.Add(rpc);
return rpc;
}
/* generates code like:
public void TargetTest (NetworkConnection conn, int param)
{
NetworkWriter writer = new NetworkWriter ();
writer.WritePackedUInt32 ((uint)param);
base.SendTargetRPCInternal (conn, typeof(class), "TargetTest", val);
}
public void CallTargetTest (NetworkConnection conn, int param)
{
// whatever the user did before
}
or if optional:
public void TargetTest (int param)
{
NetworkWriter writer = new NetworkWriter ();
writer.WritePackedUInt32 ((uint)param);
base.SendTargetRPCInternal (null, typeof(class), "TargetTest", val);
}
public void CallTargetTest (int param)
{
// whatever the user did before
}
Originally HLAPI put the send message code inside the Call function
and then proceeded to replace every call to TargetTest with CallTargetTest
This method moves all the user's code into the "CallTargetRpc" method
and replaces the body of the original method with the send message code.
This way we do not need to modify the code anywhere else, and this works
correctly in dependent assemblies
*/
public static MethodDefinition ProcessTargetRpcCall(WeaverTypes weaverTypes, Writers writers, Logger Log, TypeDefinition td, MethodDefinition md, CustomAttribute targetRpcAttr, ref bool WeavingFailed)
{
MethodDefinition rpc = MethodProcessor.SubstituteMethod(Log, td, md, ref WeavingFailed);
ILProcessor worker = md.Body.GetILProcessor();
NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes);
NetworkBehaviourProcessor.WriteGetWriter(worker, weaverTypes);
// write all the arguments that the user passed to the TargetRpc call
// (skip first one if first one is NetworkConnection)
if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.TargetRpc, ref WeavingFailed))
return null;
// invoke SendInternal and return
// this
worker.Emit(OpCodes.Ldarg_0);
if (HasNetworkConnectionParameter(md))
{
// connection
worker.Emit(OpCodes.Ldarg_1);
}
else
{
// null
worker.Emit(OpCodes.Ldnull);
}
// pass full function name to avoid ClassA.Func <-> ClassB.Func collisions
worker.Emit(OpCodes.Ldstr, md.FullName);
// pass the function hash so we don't have to compute it at runtime
// otherwise each GetStableHash call requires O(N) complexity.
// noticeable for long function names:
// https://github.com/MirrorNetworking/Mirror/issues/3375
worker.Emit(OpCodes.Ldc_I4, md.FullName.GetStableHashCode());
// writer
worker.Emit(OpCodes.Ldloc_0);
worker.Emit(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0));
worker.Emit(OpCodes.Callvirt, weaverTypes.sendTargetRpcInternal);
NetworkBehaviourProcessor.WriteReturnWriter(worker, weaverTypes);
worker.Emit(OpCodes.Ret);
return rpc;
}
}
}

View File

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

View File

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

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

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

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6905230c3c4c4e158760065a93380e83
timeCreated: 1629348618

View File

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

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,21 @@
{
"name": "Unity.Mirror.CodeGen",
"rootNamespace": "",
"references": [
"GUID:30817c1a0e6d646d99c048fc403f5979"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": true,
"precompiledReferences": [
"Mono.CecilX.dll",
"Mono.CecilX.Rocks.dll"
],
"autoReferenced": false,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

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

View File

@ -0,0 +1,267 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Mono.CecilX;
using Mono.CecilX.Cil;
using Mono.CecilX.Rocks;
namespace Mirror.Weaver
{
// not static, because ILPostProcessor is multithreaded
internal class Weaver
{
// generated code class
public const string GeneratedCodeNamespace = "Mirror";
public const string GeneratedCodeClassName = "GeneratedNetworkCode";
TypeDefinition GeneratedCodeClass;
// for resolving Mirror.dll in ReaderWriterProcessor, we need to know
// Mirror.dll name
public const string MirrorAssemblyName = "Mirror";
WeaverTypes weaverTypes;
SyncVarAccessLists syncVarAccessLists;
AssemblyDefinition CurrentAssembly;
Writers writers;
Readers readers;
// in case of weaver errors, we don't stop immediately.
// we log all errors and then eventually return false if
// weaving has failed.
// this way the user can fix multiple errors at once, instead of having
// to fix -> recompile -> fix -> recompile for one error at a time.
bool WeavingFailed;
// logger functions can be set from the outside.
// for example, Debug.Log or ILPostProcessor Diagnostics log for
// multi threaded logging.
public Logger Log;
// remote actions now support overloads,
// -> but IL2CPP doesnt like it when two generated methods
// -> have the same signature,
// -> so, append the signature to the generated method name,
// -> to create a unique name
// Example:
// RpcTeleport(Vector3 position) -> InvokeUserCode_RpcTeleport__Vector3()
// RpcTeleport(Vector3 position, Quaternion rotation) -> InvokeUserCode_RpcTeleport__Vector3Quaternion()
// fixes https://github.com/vis2k/Mirror/issues/3060
public static string GenerateMethodName(string initialPrefix, MethodDefinition md)
{
initialPrefix += md.Name;
for (int i = 0; i < md.Parameters.Count; ++i)
{
// with __ so it's more obvious that this is the parameter suffix.
// otherwise RpcTest(int) => RpcTestInt(int) which is not obvious.
initialPrefix += $"__{md.Parameters[i].ParameterType.Name}";
}
return initialPrefix;
}
public Weaver(Logger Log)
{
this.Log = Log;
}
// returns 'true' if modified (=if we did anything)
bool WeaveNetworkBehavior(TypeDefinition td)
{
if (!td.IsClass)
return false;
if (!td.IsDerivedFrom<NetworkBehaviour>())
{
if (td.IsDerivedFrom<UnityEngine.MonoBehaviour>())
MonoBehaviourProcessor.Process(Log, td, ref WeavingFailed);
return false;
}
// process this and base classes from parent to child order
List<TypeDefinition> behaviourClasses = new List<TypeDefinition>();
TypeDefinition parent = td;
while (parent != null)
{
if (parent.Is<NetworkBehaviour>())
{
break;
}
try
{
behaviourClasses.Insert(0, parent);
parent = parent.BaseType.Resolve();
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
//Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
break;
}
}
bool modified = false;
foreach (TypeDefinition behaviour in behaviourClasses)
{
modified |= new NetworkBehaviourProcessor(CurrentAssembly, weaverTypes, syncVarAccessLists, writers, readers, Log, behaviour).Process(ref WeavingFailed);
}
return modified;
}
bool WeaveModule(ModuleDefinition moduleDefinition)
{
bool modified = false;
Stopwatch watch = Stopwatch.StartNew();
watch.Start();
// ModuleDefinition.Types only finds top level types.
// GetAllTypes recursively finds all nested types as well.
// fixes nested types not being weaved, for example:
// class Parent { // ModuleDefinition.Types finds this
// class Child { // .Types.NestedTypes finds this
// class GrandChild {} // only GetAllTypes finds this too
// }
// }
// note this is not about inheritance, only about type definitions.
// see test: NetworkBehaviourTests.DeeplyNested()
foreach (TypeDefinition td in moduleDefinition.GetAllTypes())
{
if (td.IsClass && td.BaseType.CanBeResolved())
{
modified |= WeaveNetworkBehavior(td);
modified |= ServerClientAttributeProcessor.Process(weaverTypes, Log, td, ref WeavingFailed);
}
}
watch.Stop();
Console.WriteLine($"Weave behaviours and messages took {watch.ElapsedMilliseconds} milliseconds");
return modified;
}
void CreateGeneratedCodeClass()
{
// create "Mirror.GeneratedNetworkCode" class which holds all
// Readers<T> and Writers<T>
GeneratedCodeClass = new TypeDefinition(GeneratedCodeNamespace, GeneratedCodeClassName,
TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed,
weaverTypes.Import<object>());
}
void ToggleWeaverFuse()
{
// // find Weaved() function
MethodDefinition func = weaverTypes.weaverFuseMethod.Resolve();
// // change return 0 to return 1
ILProcessor worker = func.Body.GetILProcessor();
func.Body.Instructions[0] = worker.Create(OpCodes.Ldc_I4_1);
}
// Weave takes an AssemblyDefinition to be compatible with both old and
// new weavers:
// * old takes a filepath, new takes a in-memory byte[]
// * old uses DefaultAssemblyResolver with added dependencies paths,
// new uses ...?
//
// => assembly: the one we are currently weaving (MyGame.dll)
// => resolver: useful in case we need to resolve any of the assembly's
// assembly.MainModule.AssemblyReferences.
// -> we can resolve ANY of them given that the resolver
// works properly (need custom one for ILPostProcessor)
// -> IMPORTANT: .Resolve() takes an AssemblyNameReference.
// those from assembly.MainModule.AssemblyReferences are
// guaranteed to be resolve-able.
// Parsing from a string for Library/.../Mirror.dll
// would not be guaranteed to be resolve-able because
// for ILPostProcessor we can't assume where Mirror.dll
// is etc.
public bool Weave(AssemblyDefinition assembly, IAssemblyResolver resolver, out bool modified)
{
WeavingFailed = false;
modified = false;
try
{
CurrentAssembly = assembly;
// fix "No writer found for ..." error
// https://github.com/vis2k/Mirror/issues/2579
// -> when restarting Unity, weaver would try to weave a DLL
// again
// -> resulting in two GeneratedNetworkCode classes (see ILSpy)
// -> the second one wouldn't have all the writer types setup
if (CurrentAssembly.MainModule.ContainsClass(GeneratedCodeNamespace, GeneratedCodeClassName))
{
//Log.Warning($"Weaver: skipping {CurrentAssembly.Name} because already weaved");
return true;
}
weaverTypes = new WeaverTypes(CurrentAssembly, Log, ref WeavingFailed);
// weaverTypes are needed for CreateGeneratedCodeClass
CreateGeneratedCodeClass();
// WeaverList depends on WeaverTypes setup because it uses Import
syncVarAccessLists = new SyncVarAccessLists();
// initialize readers & writers with this assembly.
// we need to do this in every Process() call.
// otherwise we would get
// "System.ArgumentException: Member ... is declared in another module and needs to be imported"
// errors when still using the previous module's reader/writer funcs.
writers = new Writers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log);
readers = new Readers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log);
Stopwatch rwstopwatch = Stopwatch.StartNew();
// Need to track modified from ReaderWriterProcessor too because it could find custom read/write functions or create functions for NetworkMessages
modified = ReaderWriterProcessor.Process(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed);
rwstopwatch.Stop();
Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds");
ModuleDefinition moduleDefinition = CurrentAssembly.MainModule;
Console.WriteLine($"Script Module: {moduleDefinition.Name}");
modified |= WeaveModule(moduleDefinition);
if (WeavingFailed)
{
return false;
}
if (modified)
{
SyncVarAttributeAccessReplacer.Process(Log, moduleDefinition, syncVarAccessLists);
// add class that holds read/write functions
moduleDefinition.Types.Add(GeneratedCodeClass);
ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly, weaverTypes, writers, readers, GeneratedCodeClass);
// DO NOT WRITE here.
// CompilationFinishedHook writes to the file.
// ILPostProcessor writes to in-memory assembly.
// it depends on the caller.
//CurrentAssembly.Write(new WriterParameters{ WriteSymbols = true });
}
// if weaving succeeded, switch on the Weaver Fuse in Mirror.dll
if (CurrentAssembly.Name.Name == MirrorAssemblyName)
{
ToggleWeaverFuse();
}
return true;
}
catch (Exception e)
{
Log.Error($"Exception :{e}");
WeavingFailed = true;
return false;
}
}
}
}

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,26 @@
using System;
using System.Runtime.Serialization;
using Mono.CecilX;
namespace Mirror.Weaver
{
[Serializable]
public abstract class WeaverException : Exception
{
public MemberReference MemberReference { get; }
protected WeaverException(string message, MemberReference member) : base(message)
{
MemberReference = member;
}
protected WeaverException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) {}
}
[Serializable]
public class GenerateWriterException : WeaverException
{
public GenerateWriterException(string message, MemberReference member) : base(message, member) {}
protected GenerateWriterException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) {}
}
}

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,171 @@
using System;
using Mono.CecilX;
using UnityEditor;
using UnityEngine;
namespace Mirror.Weaver
{
// not static, because ILPostProcessor is multithreaded
public class WeaverTypes
{
public MethodReference ScriptableObjectCreateInstanceMethod;
public FieldReference NetworkBehaviourDirtyBitsReference;
public MethodReference GetWriterReference;
public MethodReference ReturnWriterReference;
public MethodReference NetworkClientConnectionReference;
public MethodReference RemoteCallDelegateConstructor;
public MethodReference NetworkServerGetActive;
public MethodReference NetworkClientGetActive;
// custom attribute types
public MethodReference InitSyncObjectReference;
// array segment
public MethodReference ArraySegmentConstructorReference;
// Action<T,T> for SyncVar Hooks
public MethodReference ActionT_T;
// syncvar
public MethodReference generatedSyncVarSetter;
public MethodReference generatedSyncVarSetter_GameObject;
public MethodReference generatedSyncVarSetter_NetworkIdentity;
public MethodReference generatedSyncVarSetter_NetworkBehaviour_T;
public MethodReference generatedSyncVarDeserialize;
public MethodReference generatedSyncVarDeserialize_GameObject;
public MethodReference generatedSyncVarDeserialize_NetworkIdentity;
public MethodReference generatedSyncVarDeserialize_NetworkBehaviour_T;
public MethodReference getSyncVarGameObjectReference;
public MethodReference getSyncVarNetworkIdentityReference;
public MethodReference getSyncVarNetworkBehaviourReference;
public MethodReference registerCommandReference;
public MethodReference registerRpcReference;
public MethodReference getTypeFromHandleReference;
public MethodReference logErrorReference;
public MethodReference logWarningReference;
public MethodReference sendCommandInternal;
public MethodReference sendRpcInternal;
public MethodReference sendTargetRpcInternal;
public MethodReference readNetworkBehaviourGeneric;
public TypeReference weaverFuseType;
public MethodReference weaverFuseMethod;
// attributes
public TypeDefinition initializeOnLoadMethodAttribute;
public TypeDefinition runtimeInitializeOnLoadMethodAttribute;
AssemblyDefinition assembly;
public TypeReference Import<T>() => Import(typeof(T));
public TypeReference Import(Type t) => assembly.MainModule.ImportReference(t);
// constructor resolves the types and stores them in fields
public WeaverTypes(AssemblyDefinition assembly, Logger Log, ref bool WeavingFailed)
{
// system types
this.assembly = assembly;
TypeReference ArraySegmentType = Import(typeof(ArraySegment<>));
ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, assembly, Log, ".ctor", ref WeavingFailed);
TypeReference ActionType = Import(typeof(Action<,>));
ActionT_T = Resolvers.ResolveMethod(ActionType, assembly, Log, ".ctor", ref WeavingFailed);
weaverFuseType = Import(typeof(WeaverFuse));
weaverFuseMethod = Resolvers.ResolveMethod(weaverFuseType, assembly, Log, "Weaved", ref WeavingFailed);
TypeReference NetworkServerType = Import(typeof(NetworkServer));
NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_active", ref WeavingFailed);
TypeReference NetworkClientType = Import(typeof(NetworkClient));
NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_active", ref WeavingFailed);
NetworkClientConnectionReference = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_connection", ref WeavingFailed);
TypeReference NetworkBehaviourType = Import<NetworkBehaviour>();
NetworkBehaviourDirtyBitsReference = Resolvers.ResolveField(NetworkBehaviourType, assembly, Log, "syncVarDirtyBits", ref WeavingFailed);
generatedSyncVarSetter = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter", ref WeavingFailed);
generatedSyncVarSetter_GameObject = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter_GameObject", ref WeavingFailed);
generatedSyncVarSetter_NetworkIdentity = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter_NetworkIdentity", ref WeavingFailed);
generatedSyncVarSetter_NetworkBehaviour_T = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarSetter_NetworkBehaviour", ref WeavingFailed);
generatedSyncVarDeserialize_GameObject = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize_GameObject", ref WeavingFailed);
generatedSyncVarDeserialize = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize", ref WeavingFailed);
generatedSyncVarDeserialize_NetworkIdentity = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize_NetworkIdentity", ref WeavingFailed);
generatedSyncVarDeserialize_NetworkBehaviour_T = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GeneratedSyncVarDeserialize_NetworkBehaviour", ref WeavingFailed);
getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarGameObject", ref WeavingFailed);
getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkIdentity", ref WeavingFailed);
getSyncVarNetworkBehaviourReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "GetSyncVarNetworkBehaviour", ref WeavingFailed);
sendCommandInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendCommandInternal", ref WeavingFailed);
sendRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendRPCInternal", ref WeavingFailed);
sendTargetRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "SendTargetRPCInternal", ref WeavingFailed);
InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, assembly, Log, "InitSyncObject", ref WeavingFailed);
TypeReference RemoteProcedureCallsType = Import(typeof(RemoteCalls.RemoteProcedureCalls));
registerCommandReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterCommand", ref WeavingFailed);
registerRpcReference = Resolvers.ResolveMethod(RemoteProcedureCallsType, assembly, Log, "RegisterRpc", ref WeavingFailed);
TypeReference RemoteCallDelegateType = Import<RemoteCalls.RemoteCallDelegate>();
RemoteCallDelegateConstructor = Resolvers.ResolveMethod(RemoteCallDelegateType, assembly, Log, ".ctor", ref WeavingFailed);
TypeReference ScriptableObjectType = Import<ScriptableObject>();
ScriptableObjectCreateInstanceMethod = Resolvers.ResolveMethod(
ScriptableObjectType, assembly, Log,
md => md.Name == "CreateInstance" && md.HasGenericParameters,
ref WeavingFailed);
TypeReference unityDebug = Import(typeof(UnityEngine.Debug));
// these have multiple methods with same name, so need to check parameters too
logErrorReference = Resolvers.ResolveMethod(unityDebug, assembly, Log, md =>
md.Name == "LogError" &&
md.Parameters.Count == 1 &&
md.Parameters[0].ParameterType.FullName == typeof(object).FullName,
ref WeavingFailed);
logWarningReference = Resolvers.ResolveMethod(unityDebug, assembly, Log, md =>
md.Name == "LogWarning" &&
md.Parameters.Count == 1 &&
md.Parameters[0].ParameterType.FullName == typeof(object).FullName,
ref WeavingFailed);
TypeReference typeType = Import(typeof(Type));
getTypeFromHandleReference = Resolvers.ResolveMethod(typeType, assembly, Log, "GetTypeFromHandle", ref WeavingFailed);
TypeReference NetworkWriterPoolType = Import(typeof(NetworkWriterPool));
GetWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Get", ref WeavingFailed);
ReturnWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Return", ref WeavingFailed);
TypeReference readerExtensions = Import(typeof(NetworkReaderExtensions));
readNetworkBehaviourGeneric = Resolvers.ResolveMethod(readerExtensions, assembly, Log, (md =>
{
return md.Name == nameof(NetworkReaderExtensions.ReadNetworkBehaviour) &&
md.HasGenericParameters;
}),
ref WeavingFailed);
// [InitializeOnLoadMethod]
// 'UnityEditor' is not available in builds.
// we can only import this attribute if we are in an Editor assembly.
if (Helpers.IsEditorAssembly(assembly))
{
TypeReference initializeOnLoadMethodAttributeRef = Import(typeof(InitializeOnLoadMethodAttribute));
initializeOnLoadMethodAttribute = initializeOnLoadMethodAttributeRef.Resolve();
}
// [RuntimeInitializeOnLoadMethod]
TypeReference runtimeInitializeOnLoadMethodAttributeRef = Import(typeof(RuntimeInitializeOnLoadMethodAttribute));
runtimeInitializeOnLoadMethodAttribute = runtimeInitializeOnLoadMethodAttributeRef.Resolve();
}
}
}

View File

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

View File

@ -0,0 +1,341 @@
using System;
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
// to use Mono.CecilX.Rocks here, we need to 'override references' in the
// Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks.
// otherwise we get an unknown import exception.
using Mono.CecilX.Rocks;
namespace Mirror.Weaver
{
// not static, because ILPostProcessor is multithreaded
public class Writers
{
// Writers are only for this assembly.
// can't be used from another assembly, otherwise we will get:
// "System.ArgumentException: Member ... is declared in another module and needs to be imported"
AssemblyDefinition assembly;
WeaverTypes weaverTypes;
TypeDefinition GeneratedCodeClass;
Logger Log;
Dictionary<TypeReference, MethodReference> writeFuncs =
new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
public Writers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log)
{
this.assembly = assembly;
this.weaverTypes = weaverTypes;
this.GeneratedCodeClass = GeneratedCodeClass;
this.Log = Log;
}
public 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.
//Log.Warning($"Registering a Write method for {dataType.FullName} when one already exists", methodReference);
}
// we need to import type when we Initialize Writers so import here in case it is used anywhere else
TypeReference imported = assembly.MainModule.ImportReference(dataType);
writeFuncs[imported] = methodReference;
}
void RegisterWriteFunc(TypeReference typeReference, MethodDefinition newWriterFunc)
{
Register(typeReference, newWriterFunc);
GeneratedCodeClass.Methods.Add(newWriterFunc);
}
// Finds existing writer for type, if non exists trys to create one
public MethodReference GetWriteFunc(TypeReference variable, ref bool WeavingFailed)
{
if (writeFuncs.TryGetValue(variable, out MethodReference foundFunc))
return foundFunc;
// this try/catch will be removed in future PR and make `GetWriteFunc` throw instead
try
{
TypeReference importedVariable = assembly.MainModule.ImportReference(variable);
return GenerateWriter(importedVariable, ref WeavingFailed);
}
catch (GenerateWriterException e)
{
Log.Error(e.Message, e.MemberReference);
WeavingFailed = true;
return null;
}
}
//Throws GenerateWriterException when writer could not be generated for type
MethodReference GenerateWriter(TypeReference variableReference, ref bool WeavingFailed)
{
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), ref WeavingFailed);
}
if (variableReference.Resolve()?.IsEnum ?? false)
{
// serialize enum as their base type
return GenerateEnumWriteFunc(variableReference, ref WeavingFailed);
}
// check for collections
if (variableReference.Is(typeof(ArraySegment<>)))
{
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
TypeReference elementType = genericInstance.GenericArguments[0];
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteArraySegment), ref WeavingFailed);
}
if (variableReference.Is(typeof(List<>)))
{
GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
TypeReference elementType = genericInstance.GenericArguments[0];
return GenerateCollectionWriter(variableReference, elementType, nameof(NetworkWriterExtensions.WriteList), ref WeavingFailed);
}
// handle both NetworkBehaviour and inheritors.
// fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
if (variableReference.IsDerivedFrom<NetworkBehaviour>() || variableReference.Is<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, ref WeavingFailed);
}
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");
}
}
MethodDefinition GenerateEnumWriteFunc(TypeReference variable, ref bool WeavingFailed)
{
MethodDefinition writerFunc = GenerateWriterFunc(variable);
ILProcessor worker = writerFunc.Body.GetILProcessor();
MethodReference underlyingWriter = GetWriteFunc(variable.Resolve().GetEnumUnderlyingType(), ref WeavingFailed);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_1);
worker.Emit(OpCodes.Call, underlyingWriter);
worker.Emit(OpCodes.Ret);
return writerFunc;
}
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;
}
MethodDefinition GenerateClassOrStructWriterFunction(TypeReference variable, ref bool WeavingFailed)
{
MethodDefinition writerFunc = GenerateWriterFunc(variable);
ILProcessor worker = writerFunc.Body.GetILProcessor();
if (!variable.Resolve().IsValueType)
WriteNullCheck(worker, ref WeavingFailed);
if (!WriteAllFields(variable, worker, ref WeavingFailed))
return null;
worker.Emit(OpCodes.Ret);
return writerFunc;
}
void WriteNullCheck(ILProcessor worker, ref bool WeavingFailed)
{
// 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>(), ref WeavingFailed));
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>(), ref WeavingFailed));
}
// Find all fields in type and write them
bool WriteAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed)
{
foreach (FieldDefinition field in variable.FindAllPublicFields())
{
MethodReference writeFunc = GetWriteFunc(field.FieldType, ref WeavingFailed);
// need this null check till later PR when GetWriteFunc throws exception instead
if (writeFunc == null) { return false; }
FieldReference fieldRef = assembly.MainModule.ImportReference(field);
worker.Emit(OpCodes.Ldarg_0);
worker.Emit(OpCodes.Ldarg_1);
worker.Emit(OpCodes.Ldfld, fieldRef);
worker.Emit(OpCodes.Call, writeFunc);
}
return true;
}
MethodDefinition GenerateCollectionWriter(TypeReference variable, TypeReference elementType, string writerFunction, ref bool WeavingFailed)
{
MethodDefinition writerFunc = GenerateWriterFunc(variable);
MethodReference elementWriteFunc = GetWriteFunc(elementType, ref WeavingFailed);
MethodReference intWriterFunc = GetWriteFunc(weaverTypes.Import<int>(), ref WeavingFailed);
// need this null check till later PR when GetWriteFunc throws exception instead
if (elementWriteFunc == null)
{
Log.Error($"Cannot generate writer for {variable}. Use a supported type or provide a custom writer", variable);
WeavingFailed = true;
return writerFunc;
}
ModuleDefinition module = assembly.MainModule;
TypeReference readerExtensions = module.ImportReference(typeof(NetworkWriterExtensions));
MethodReference collectionWriter = Resolvers.ResolveMethod(readerExtensions, assembly, Log, writerFunction, ref WeavingFailed);
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;
}
// Save a delegate for each one of the writers into Writer{T}.write
internal void InitializeWriters(ILProcessor worker)
{
ModuleDefinition module = assembly.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(assembly.MainModule, actionGenericInstance);
worker.Emit(OpCodes.Newobj, actionRefInstance);
// save it in Writer<T>.write
GenericInstanceType genericInstance = genericWriterClassRef.MakeGenericInstanceType(targetType);
FieldReference specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance);
worker.Emit(OpCodes.Stsfld, specializedField);
}
}
}
}

View File

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