Weaver.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using Mono.CecilX;
  5. using Mono.CecilX.Cil;
  6. using Mono.CecilX.Rocks;
  7. namespace Mirror.Weaver
  8. {
  9. // not static, because ILPostProcessor is multithreaded
  10. internal class Weaver
  11. {
  12. public const string InvokeRpcPrefix = "InvokeUserCode_";
  13. // generated code class
  14. public const string GeneratedCodeNamespace = "Mirror";
  15. public const string GeneratedCodeClassName = "GeneratedNetworkCode";
  16. TypeDefinition GeneratedCodeClass;
  17. // for resolving Mirror.dll in ReaderWriterProcessor, we need to know
  18. // Mirror.dll name
  19. public const string MirrorAssemblyName = "Mirror";
  20. WeaverTypes weaverTypes;
  21. SyncVarAccessLists syncVarAccessLists;
  22. AssemblyDefinition CurrentAssembly;
  23. Writers writers;
  24. Readers readers;
  25. // in case of weaver errors, we don't stop immediately.
  26. // we log all errors and then eventually return false if
  27. // weaving has failed.
  28. // this way the user can fix multiple errors at once, instead of having
  29. // to fix -> recompile -> fix -> recompile for one error at a time.
  30. bool WeavingFailed;
  31. // logger functions can be set from the outside.
  32. // for example, Debug.Log or ILPostProcessor Diagnostics log for
  33. // multi threaded logging.
  34. public Logger Log;
  35. // remote actions now support overloads,
  36. // -> but IL2CPP doesnt like it when two generated methods
  37. // -> have the same signature,
  38. // -> so, append the signature to the generated method name,
  39. // -> to create a unique name
  40. // Example:
  41. // RpcTeleport(Vector3 position) -> InvokeUserCode_RpcTeleport__Vector3()
  42. // RpcTeleport(Vector3 position, Quaternion rotation) -> InvokeUserCode_RpcTeleport__Vector3Quaternion()
  43. // fixes https://github.com/vis2k/Mirror/issues/3060
  44. public static string GenerateMethodName(string initialPrefix, MethodDefinition md)
  45. {
  46. initialPrefix += md.Name;
  47. for (int i = 0; i < md.Parameters.Count; ++i)
  48. {
  49. // with __ so it's more obvious that this is the parameter suffix.
  50. // otherwise RpcTest(int) => RpcTestInt(int) which is not obvious.
  51. initialPrefix += $"__{md.Parameters[i].ParameterType.Name}";
  52. }
  53. return initialPrefix;
  54. }
  55. public Weaver(Logger Log)
  56. {
  57. this.Log = Log;
  58. }
  59. // returns 'true' if modified (=if we did anything)
  60. bool WeaveNetworkBehavior(TypeDefinition td)
  61. {
  62. if (!td.IsClass)
  63. return false;
  64. if (!td.IsDerivedFrom<NetworkBehaviour>())
  65. {
  66. if (td.IsDerivedFrom<UnityEngine.MonoBehaviour>())
  67. MonoBehaviourProcessor.Process(Log, td, ref WeavingFailed);
  68. return false;
  69. }
  70. // process this and base classes from parent to child order
  71. List<TypeDefinition> behaviourClasses = new List<TypeDefinition>();
  72. TypeDefinition parent = td;
  73. while (parent != null)
  74. {
  75. if (parent.Is<NetworkBehaviour>())
  76. {
  77. break;
  78. }
  79. try
  80. {
  81. behaviourClasses.Insert(0, parent);
  82. parent = parent.BaseType.Resolve();
  83. }
  84. catch (AssemblyResolutionException)
  85. {
  86. // this can happen for plugins.
  87. //Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
  88. break;
  89. }
  90. }
  91. bool modified = false;
  92. foreach (TypeDefinition behaviour in behaviourClasses)
  93. {
  94. modified |= new NetworkBehaviourProcessor(CurrentAssembly, weaverTypes, syncVarAccessLists, writers, readers, Log, behaviour).Process(ref WeavingFailed);
  95. }
  96. return modified;
  97. }
  98. bool WeaveModule(ModuleDefinition moduleDefinition)
  99. {
  100. bool modified = false;
  101. Stopwatch watch = Stopwatch.StartNew();
  102. watch.Start();
  103. // ModuleDefinition.Types only finds top level types.
  104. // GetAllTypes recursively finds all nested types as well.
  105. // fixes nested types not being weaved, for example:
  106. // class Parent { // ModuleDefinition.Types finds this
  107. // class Child { // .Types.NestedTypes finds this
  108. // class GrandChild {} // only GetAllTypes finds this too
  109. // }
  110. // }
  111. // note this is not about inheritance, only about type definitions.
  112. // see test: NetworkBehaviourTests.DeeplyNested()
  113. foreach (TypeDefinition td in moduleDefinition.GetAllTypes())
  114. {
  115. if (td.IsClass && td.BaseType.CanBeResolved())
  116. {
  117. modified |= WeaveNetworkBehavior(td);
  118. modified |= ServerClientAttributeProcessor.Process(weaverTypes, Log, td, ref WeavingFailed);
  119. }
  120. }
  121. watch.Stop();
  122. Console.WriteLine($"Weave behaviours and messages took {watch.ElapsedMilliseconds} milliseconds");
  123. return modified;
  124. }
  125. void CreateGeneratedCodeClass()
  126. {
  127. // create "Mirror.GeneratedNetworkCode" class which holds all
  128. // Readers<T> and Writers<T>
  129. GeneratedCodeClass = new TypeDefinition(GeneratedCodeNamespace, GeneratedCodeClassName,
  130. TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.Abstract | TypeAttributes.Sealed,
  131. weaverTypes.Import<object>());
  132. }
  133. void ToggleWeaverFuse()
  134. {
  135. // // find Weaved() function
  136. MethodDefinition func = weaverTypes.weaverFuseMethod.Resolve();
  137. // // change return 0 to return 1
  138. ILProcessor worker = func.Body.GetILProcessor();
  139. func.Body.Instructions[0] = worker.Create(OpCodes.Ldc_I4_1);
  140. }
  141. // Weave takes an AssemblyDefinition to be compatible with both old and
  142. // new weavers:
  143. // * old takes a filepath, new takes a in-memory byte[]
  144. // * old uses DefaultAssemblyResolver with added dependencies paths,
  145. // new uses ...?
  146. //
  147. // => assembly: the one we are currently weaving (MyGame.dll)
  148. // => resolver: useful in case we need to resolve any of the assembly's
  149. // assembly.MainModule.AssemblyReferences.
  150. // -> we can resolve ANY of them given that the resolver
  151. // works properly (need custom one for ILPostProcessor)
  152. // -> IMPORTANT: .Resolve() takes an AssemblyNameReference.
  153. // those from assembly.MainModule.AssemblyReferences are
  154. // guaranteed to be resolve-able.
  155. // Parsing from a string for Library/.../Mirror.dll
  156. // would not be guaranteed to be resolve-able because
  157. // for ILPostProcessor we can't assume where Mirror.dll
  158. // is etc.
  159. public bool Weave(AssemblyDefinition assembly, IAssemblyResolver resolver, out bool modified)
  160. {
  161. WeavingFailed = false;
  162. modified = false;
  163. try
  164. {
  165. CurrentAssembly = assembly;
  166. // fix "No writer found for ..." error
  167. // https://github.com/vis2k/Mirror/issues/2579
  168. // -> when restarting Unity, weaver would try to weave a DLL
  169. // again
  170. // -> resulting in two GeneratedNetworkCode classes (see ILSpy)
  171. // -> the second one wouldn't have all the writer types setup
  172. if (CurrentAssembly.MainModule.ContainsClass(GeneratedCodeNamespace, GeneratedCodeClassName))
  173. {
  174. //Log.Warning($"Weaver: skipping {CurrentAssembly.Name} because already weaved");
  175. return true;
  176. }
  177. weaverTypes = new WeaverTypes(CurrentAssembly, Log, ref WeavingFailed);
  178. // weaverTypes are needed for CreateGeneratedCodeClass
  179. CreateGeneratedCodeClass();
  180. // WeaverList depends on WeaverTypes setup because it uses Import
  181. syncVarAccessLists = new SyncVarAccessLists();
  182. // initialize readers & writers with this assembly.
  183. // we need to do this in every Process() call.
  184. // otherwise we would get
  185. // "System.ArgumentException: Member ... is declared in another module and needs to be imported"
  186. // errors when still using the previous module's reader/writer funcs.
  187. writers = new Writers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log);
  188. readers = new Readers(CurrentAssembly, weaverTypes, GeneratedCodeClass, Log);
  189. Stopwatch rwstopwatch = Stopwatch.StartNew();
  190. // Need to track modified from ReaderWriterProcessor too because it could find custom read/write functions or create functions for NetworkMessages
  191. modified = ReaderWriterProcessor.Process(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed);
  192. rwstopwatch.Stop();
  193. Console.WriteLine($"Find all reader and writers took {rwstopwatch.ElapsedMilliseconds} milliseconds");
  194. ModuleDefinition moduleDefinition = CurrentAssembly.MainModule;
  195. Console.WriteLine($"Script Module: {moduleDefinition.Name}");
  196. modified |= WeaveModule(moduleDefinition);
  197. if (WeavingFailed)
  198. {
  199. return false;
  200. }
  201. if (modified)
  202. {
  203. SyncVarAttributeAccessReplacer.Process(Log, moduleDefinition, syncVarAccessLists);
  204. // add class that holds read/write functions
  205. moduleDefinition.Types.Add(GeneratedCodeClass);
  206. ReaderWriterProcessor.InitializeReaderAndWriters(CurrentAssembly, weaverTypes, writers, readers, GeneratedCodeClass);
  207. // DO NOT WRITE here.
  208. // CompilationFinishedHook writes to the file.
  209. // ILPostProcessor writes to in-memory assembly.
  210. // it depends on the caller.
  211. //CurrentAssembly.Write(new WriterParameters{ WriteSymbols = true });
  212. }
  213. // if weaving succeeded, switch on the Weaver Fuse in Mirror.dll
  214. if (CurrentAssembly.Name.Name == MirrorAssemblyName)
  215. {
  216. ToggleWeaverFuse();
  217. }
  218. return true;
  219. }
  220. catch (Exception e)
  221. {
  222. Log.Error($"Exception :{e}");
  223. WeavingFailed = true;
  224. return false;
  225. }
  226. }
  227. }
  228. }