Weaver.cs 9.9 KB

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