This commit is contained in:
2025-06-16 15:14:23 +02:00
committed by devbeni
parent 60fe4620ff
commit 4ff561284f
3174 changed files with 428263 additions and 0 deletions

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,10 @@
fileFormatVersion: 2
guid: 9009d1db4ed44f6694a92bf8ad7738e9
timeCreated: 1630129423
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/EntryPointILPostProcessor/CompiledAssemblyFromFile.cs
uploadId: 736421

View File

@ -0,0 +1,205 @@
// 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!
// the fix for #2503 started showing this warning for Bee.BeeDriver on mac,
// which is for compilation. we can ignore that one.
if (!name.Name.StartsWith("Bee.BeeDriver"))
{
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,10 @@
fileFormatVersion: 2
guid: 0b3e94696e22440ead0b3a42411bbe14
timeCreated: 1629693784
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/EntryPointILPostProcessor/ILPostProcessorAssemblyResolver.cs
uploadId: 736421

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,10 @@
fileFormatVersion: 2
guid: 2a4b115486b74d27a9540f3c39ae2d46
timeCreated: 1630152191
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/EntryPointILPostProcessor/ILPostProcessorFromFile.cs
uploadId: 736421

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,10 @@
fileFormatVersion: 2
guid: 5f113eb695b348b5b28cd85358c8959a
timeCreated: 1628859074
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/EntryPointILPostProcessor/ILPostProcessorHook.cs
uploadId: 736421

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,10 @@
fileFormatVersion: 2
guid: e7b56e7826664e34a415e4b70d958f2a
timeCreated: 1629533154
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/EntryPointILPostProcessor/ILPostProcessorLogger.cs
uploadId: 736421

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,10 @@
fileFormatVersion: 2
guid: 6403a7e3b3ae4e009ae282f111d266e0
timeCreated: 1629709256
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/EntryPointILPostProcessor/ILPostProcessorReflectionImporter.cs
uploadId: 736421

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,10 @@
fileFormatVersion: 2
guid: a1003b568bad4e69b961c4c81d5afd96
timeCreated: 1629709223
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Editor/Weaver/EntryPointILPostProcessor/ILPostProcessorReflectionImporterProvider.cs
uploadId: 736421