ILPostProcessorHook.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. // hook via ILPostProcessor from Unity 2020.3+
  2. // (2020.1 has errors https://github.com/vis2k/Mirror/issues/2912)
  3. #if UNITY_2020_3_OR_NEWER
  4. // Unity.CompilationPipeline reference is only resolved if assembly name is
  5. // Unity.*.CodeGen:
  6. // https://forum.unity.com/threads/how-does-unity-do-codegen-and-why-cant-i-do-it-myself.853867/#post-5646937
  7. using System.IO;
  8. using System.Linq;
  9. // to use Mono.CecilX here, we need to 'override references' in the
  10. // Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.
  11. // otherwise we get a reflection exception with 'file not found: CecilX'.
  12. using Mono.CecilX;
  13. using Mono.CecilX.Cil;
  14. using Unity.CompilationPipeline.Common.ILPostProcessing;
  15. // IMPORTANT: 'using UnityEngine' does not work in here.
  16. // Unity gives "(0,0): error System.Security.SecurityException: ECall methods must be packaged into a system module."
  17. //using UnityEngine;
  18. namespace Mirror.Weaver
  19. {
  20. public class ILPostProcessorHook : ILPostProcessor
  21. {
  22. // from CompilationFinishedHook
  23. const string MirrorRuntimeAssemblyName = "Mirror";
  24. // ILPostProcessor is invoked by Unity.
  25. // we can not tell it to ignore certain assemblies before processing.
  26. // add a 'ignore' define for convenience.
  27. // => WeaverTests/WeaverAssembler need it to avoid Unity running it
  28. public const string IgnoreDefine = "ILPP_IGNORE";
  29. // we can't use Debug.Log in ILPP, so we need a custom logger
  30. ILPostProcessorLogger Log = new ILPostProcessorLogger();
  31. // ???
  32. public override ILPostProcessor GetInstance() => this;
  33. // check if assembly has the 'ignore' define
  34. static bool HasDefine(ICompiledAssembly assembly, string define) =>
  35. assembly.Defines != null &&
  36. assembly.Defines.Contains(define);
  37. // process Mirror, or anything that references Mirror
  38. public override bool WillProcess(ICompiledAssembly compiledAssembly)
  39. {
  40. // compiledAssembly.References are file paths:
  41. // Library/Bee/artifacts/200b0aE.dag/Mirror.CompilerSymbols.dll
  42. // Assets/Mirror/Plugins/Mono.Cecil/Mono.CecilX.dll
  43. // /Applications/Unity/Hub/Editor/2021.2.0b6_apple_silicon/Unity.app/Contents/NetStandard/ref/2.1.0/netstandard.dll
  44. //
  45. // log them to see:
  46. // foreach (string reference in compiledAssembly.References)
  47. // LogDiagnostics($"{compiledAssembly.Name} references {reference}");
  48. bool relevant = compiledAssembly.Name == MirrorRuntimeAssemblyName ||
  49. compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == MirrorRuntimeAssemblyName);
  50. bool ignore = HasDefine(compiledAssembly, IgnoreDefine);
  51. return relevant && !ignore;
  52. }
  53. public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
  54. {
  55. //Log.Warning($"Processing {compiledAssembly.Name}");
  56. // load the InMemoryAssembly peData into a MemoryStream
  57. byte[] peData = compiledAssembly.InMemoryAssembly.PeData;
  58. //LogDiagnostics($" peData.Length={peData.Length} bytes");
  59. using (MemoryStream stream = new MemoryStream(peData))
  60. using (ILPostProcessorAssemblyResolver asmResolver = new ILPostProcessorAssemblyResolver(compiledAssembly, Log))
  61. {
  62. // we need to load symbols. otherwise we get:
  63. // "(0,0): error Mono.CecilX.Cil.SymbolsNotFoundException: No symbol found for file: "
  64. using (MemoryStream symbols = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData))
  65. {
  66. ReaderParameters readerParameters = new ReaderParameters{
  67. SymbolStream = symbols,
  68. ReadWrite = true,
  69. ReadSymbols = true,
  70. AssemblyResolver = asmResolver,
  71. // custom reflection importer to fix System.Private.CoreLib
  72. // not being found in custom assembly resolver above.
  73. ReflectionImporterProvider = new ILPostProcessorReflectionImporterProvider()
  74. };
  75. using (AssemblyDefinition asmDef = AssemblyDefinition.ReadAssembly(stream, readerParameters))
  76. {
  77. // resolving a Mirror.dll type like NetworkServer while
  78. // weaving Mirror.dll does not work. it throws a
  79. // NullReferenceException in WeaverTypes.ctor
  80. // when Resolve() is called on the first Mirror type.
  81. // need to add the AssemblyDefinition itself to use.
  82. asmResolver.SetAssemblyDefinitionForCompiledAssembly(asmDef);
  83. // weave this assembly.
  84. Weaver weaver = new Weaver(Log);
  85. if (weaver.Weave(asmDef, asmResolver, out bool modified))
  86. {
  87. //Log.Warning($"Weaving succeeded for: {compiledAssembly.Name}");
  88. // write if modified
  89. if (modified)
  90. {
  91. // when weaving Mirror.dll with ILPostProcessor,
  92. // Weave() -> WeaverTypes -> resolving the first
  93. // type in Mirror.dll adds a reference to
  94. // Mirror.dll even though we are in Mirror.dll.
  95. // -> this would throw an exception:
  96. // "Mirror references itself" and not compile
  97. // -> need to detect and fix manually here
  98. if (asmDef.MainModule.AssemblyReferences.Any(r => r.Name == asmDef.Name.Name))
  99. {
  100. asmDef.MainModule.AssemblyReferences.Remove(asmDef.MainModule.AssemblyReferences.First(r => r.Name == asmDef.Name.Name));
  101. //Log.Warning($"fixed self referencing Assembly: {asmDef.Name.Name}");
  102. }
  103. MemoryStream peOut = new MemoryStream();
  104. MemoryStream pdbOut = new MemoryStream();
  105. WriterParameters writerParameters = new WriterParameters
  106. {
  107. SymbolWriterProvider = new PortablePdbWriterProvider(),
  108. SymbolStream = pdbOut,
  109. WriteSymbols = true
  110. };
  111. asmDef.Write(peOut, writerParameters);
  112. InMemoryAssembly inMemory = new InMemoryAssembly(peOut.ToArray(), pdbOut.ToArray());
  113. return new ILPostProcessResult(inMemory, Log.Logs);
  114. }
  115. }
  116. // if anything during Weave() fails, we log an error.
  117. // don't need to indicate 'weaving failed' again.
  118. // in fact, this would break tests only expecting certain errors.
  119. //else Log.Error($"Weaving failed for: {compiledAssembly.Name}");
  120. }
  121. }
  122. }
  123. // always return an ILPostProcessResult with Logs.
  124. // otherwise we won't see Logs if weaving failed.
  125. return new ILPostProcessResult(compiledAssembly.InMemoryAssembly, Log.Logs);
  126. }
  127. }
  128. }
  129. #endif