ReaderWriterProcessor.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. // finds all readers and writers and register them
  2. using System.Linq;
  3. using Mono.CecilX;
  4. using Mono.CecilX.Cil;
  5. using Mono.CecilX.Rocks;
  6. using UnityEngine;
  7. namespace Mirror.Weaver
  8. {
  9. public static class ReaderWriterProcessor
  10. {
  11. public static bool Process(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed)
  12. {
  13. // find NetworkReader/Writer extensions from Mirror.dll first.
  14. // and NetworkMessage custom writer/reader extensions.
  15. // NOTE: do not include this result in our 'modified' return value,
  16. // otherwise Unity crashes when running tests
  17. ProcessMirrorAssemblyClasses(CurrentAssembly, resolver, Log, writers, readers, ref WeavingFailed);
  18. // find readers/writers in the assembly we are in right now.
  19. return ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly, writers, readers, ref WeavingFailed);
  20. }
  21. static void ProcessMirrorAssemblyClasses(AssemblyDefinition CurrentAssembly, IAssemblyResolver resolver, Logger Log, Writers writers, Readers readers, ref bool WeavingFailed)
  22. {
  23. // find Mirror.dll in assembly's references.
  24. // those are guaranteed to be resolvable and correct.
  25. // after all, it references them :)
  26. AssemblyNameReference mirrorAssemblyReference = CurrentAssembly.MainModule.FindReference(Weaver.MirrorAssemblyName);
  27. if (mirrorAssemblyReference != null)
  28. {
  29. // resolve the assembly to load the AssemblyDefinition.
  30. // we need to search all types in it.
  31. // if we only were to resolve one known type like in WeaverTypes,
  32. // then we wouldn't need it.
  33. AssemblyDefinition mirrorAssembly = resolver.Resolve(mirrorAssemblyReference);
  34. if (mirrorAssembly != null)
  35. {
  36. ProcessAssemblyClasses(CurrentAssembly, mirrorAssembly, writers, readers, ref WeavingFailed);
  37. }
  38. else Log.Error($"Failed to resolve {mirrorAssemblyReference}");
  39. }
  40. else Log.Error("Failed to find Mirror AssemblyNameReference. Can't register Mirror.dll readers/writers.");
  41. }
  42. static bool ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly, Writers writers, Readers readers, ref bool WeavingFailed)
  43. {
  44. bool modified = false;
  45. foreach (TypeDefinition klass in assembly.MainModule.Types)
  46. {
  47. // extension methods only live in static classes
  48. // static classes are represented as sealed and abstract
  49. if (klass.IsAbstract && klass.IsSealed)
  50. {
  51. // if assembly has any declared writers then it is "modified"
  52. modified |= LoadDeclaredWriters(CurrentAssembly, klass, writers);
  53. modified |= LoadDeclaredReaders(CurrentAssembly, klass, readers);
  54. }
  55. }
  56. foreach (TypeDefinition klass in assembly.MainModule.Types)
  57. {
  58. // if assembly has any network message then it is modified
  59. modified |= LoadMessageReadWriter(CurrentAssembly.MainModule, writers, readers, klass, ref WeavingFailed);
  60. }
  61. return modified;
  62. }
  63. static bool LoadMessageReadWriter(ModuleDefinition module, Writers writers, Readers readers, TypeDefinition klass, ref bool WeavingFailed)
  64. {
  65. bool modified = false;
  66. if (!klass.IsAbstract && !klass.IsInterface && klass.ImplementsInterface<NetworkMessage>())
  67. {
  68. readers.GetReadFunc(module.ImportReference(klass), ref WeavingFailed);
  69. writers.GetWriteFunc(module.ImportReference(klass), ref WeavingFailed);
  70. modified = true;
  71. }
  72. foreach (TypeDefinition td in klass.NestedTypes)
  73. {
  74. modified |= LoadMessageReadWriter(module, writers, readers, td, ref WeavingFailed);
  75. }
  76. return modified;
  77. }
  78. static bool LoadDeclaredWriters(AssemblyDefinition currentAssembly, TypeDefinition klass, Writers writers)
  79. {
  80. // register all the writers in this class. Skip the ones with wrong signature
  81. bool modified = false;
  82. foreach (MethodDefinition method in klass.Methods)
  83. {
  84. if (method.Parameters.Count != 2)
  85. continue;
  86. if (!method.Parameters[0].ParameterType.Is<NetworkWriter>())
  87. continue;
  88. if (!method.ReturnType.Is(typeof(void)))
  89. continue;
  90. if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
  91. continue;
  92. if (method.HasGenericParameters)
  93. continue;
  94. TypeReference dataType = method.Parameters[1].ParameterType;
  95. writers.Register(dataType, currentAssembly.MainModule.ImportReference(method));
  96. modified = true;
  97. }
  98. return modified;
  99. }
  100. static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefinition klass, Readers readers)
  101. {
  102. // register all the reader in this class. Skip the ones with wrong signature
  103. bool modified = false;
  104. foreach (MethodDefinition method in klass.Methods)
  105. {
  106. if (method.Parameters.Count != 1)
  107. continue;
  108. if (!method.Parameters[0].ParameterType.Is<NetworkReader>())
  109. continue;
  110. if (method.ReturnType.Is(typeof(void)))
  111. continue;
  112. if (!method.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>())
  113. continue;
  114. if (method.HasGenericParameters)
  115. continue;
  116. readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method));
  117. modified = true;
  118. }
  119. return modified;
  120. }
  121. // helper function to add [RuntimeInitializeOnLoad] attribute to method
  122. static void AddRuntimeInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method)
  123. {
  124. // NOTE: previously we used reflection because according paul,
  125. // 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong
  126. // order, which breaks rewired'
  127. // it's not obvious why importing an attribute via reflection instead
  128. // of cecil would break anything. let's use cecil.
  129. // to add a CustomAttribute, we need the attribute's constructor.
  130. // in this case, there are two: empty, and RuntimeInitializeOnLoadType.
  131. // we want the last one, with the type parameter.
  132. MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().Last();
  133. //MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().First();
  134. // using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported
  135. // we need to import it first.
  136. CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor));
  137. // add the RuntimeInitializeLoadType.BeforeSceneLoad argument to ctor
  138. attribute.ConstructorArguments.Add(new CustomAttributeArgument(weaverTypes.Import<RuntimeInitializeLoadType>(), RuntimeInitializeLoadType.BeforeSceneLoad));
  139. method.CustomAttributes.Add(attribute);
  140. }
  141. // helper function to add [InitializeOnLoad] attribute to method
  142. // (only works in Editor assemblies. check IsEditorAssembly first.)
  143. static void AddInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method)
  144. {
  145. // NOTE: previously we used reflection because according paul,
  146. // 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong
  147. // order, which breaks rewired'
  148. // it's not obvious why importing an attribute via reflection instead
  149. // of cecil would break anything. let's use cecil.
  150. // to add a CustomAttribute, we need the attribute's constructor.
  151. // in this case, there's only one - and it's an empty constructor.
  152. MethodDefinition ctor = weaverTypes.initializeOnLoadMethodAttribute.GetConstructors().First();
  153. // using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported
  154. // we need to import it first.
  155. CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor));
  156. method.CustomAttributes.Add(attribute);
  157. }
  158. // adds Mirror.GeneratedNetworkCode.InitReadWriters() method that
  159. // registers all generated writers into Mirror.Writer<T> static class.
  160. // -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime
  161. // -> uses [InitializeOnLoad] if UnityEditor is referenced so it works
  162. // in Editor and in tests too
  163. //
  164. // use ILSpy to see the result (it's in the DLL's 'Mirror' namespace)
  165. public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly, WeaverTypes weaverTypes, Writers writers, Readers readers, TypeDefinition GeneratedCodeClass)
  166. {
  167. MethodDefinition initReadWriters = new MethodDefinition("InitReadWriters", MethodAttributes.Public |
  168. MethodAttributes.Static,
  169. weaverTypes.Import(typeof(void)));
  170. // add [RuntimeInitializeOnLoad] in any case
  171. AddRuntimeInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters);
  172. // add [InitializeOnLoad] if UnityEditor is referenced
  173. if (Helpers.IsEditorAssembly(currentAssembly))
  174. {
  175. AddInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters);
  176. }
  177. // fill function body with reader/writer initializers
  178. ILProcessor worker = initReadWriters.Body.GetILProcessor();
  179. // for debugging: add a log to see if initialized on load
  180. //worker.Emit(OpCodes.Ldstr, $"[InitReadWriters] called!");
  181. //worker.Emit(OpCodes.Call, Weaver.weaverTypes.logWarningReference);
  182. writers.InitializeWriters(worker);
  183. readers.InitializeReaders(worker);
  184. worker.Emit(OpCodes.Ret);
  185. GeneratedCodeClass.Methods.Add(initReadWriters);
  186. }
  187. }
  188. }