Readers.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. using System;
  2. using System.Collections.Generic;
  3. using Mono.CecilX;
  4. using Mono.CecilX.Cil;
  5. // to use Mono.CecilX.Rocks here, we need to 'override references' in the
  6. // Unity.Mirror.CodeGen assembly definition file in the Editor, and add CecilX.Rocks.
  7. // otherwise we get an unknown import exception.
  8. using Mono.CecilX.Rocks;
  9. namespace Mirror.Weaver
  10. {
  11. // not static, because ILPostProcessor is multithreaded
  12. public class Readers
  13. {
  14. // Readers are only for this assembly.
  15. // can't be used from another assembly, otherwise we will get:
  16. // "System.ArgumentException: Member ... is declared in another module and needs to be imported"
  17. AssemblyDefinition assembly;
  18. WeaverTypes weaverTypes;
  19. TypeDefinition GeneratedCodeClass;
  20. Logger Log;
  21. Dictionary<TypeReference, MethodReference> readFuncs =
  22. new Dictionary<TypeReference, MethodReference>(new TypeReferenceComparer());
  23. public Readers(AssemblyDefinition assembly, WeaverTypes weaverTypes, TypeDefinition GeneratedCodeClass, Logger Log)
  24. {
  25. this.assembly = assembly;
  26. this.weaverTypes = weaverTypes;
  27. this.GeneratedCodeClass = GeneratedCodeClass;
  28. this.Log = Log;
  29. }
  30. internal void Register(TypeReference dataType, MethodReference methodReference)
  31. {
  32. if (readFuncs.ContainsKey(dataType))
  33. {
  34. // TODO enable this again later.
  35. // Reader has some obsolete functions that were renamed.
  36. // Don't want weaver warnings for all of them.
  37. //Log.Warning($"Registering a Read method for {dataType.FullName} when one already exists", methodReference);
  38. }
  39. // we need to import type when we Initialize Readers so import here in case it is used anywhere else
  40. TypeReference imported = assembly.MainModule.ImportReference(dataType);
  41. readFuncs[imported] = methodReference;
  42. }
  43. void RegisterReadFunc(TypeReference typeReference, MethodDefinition newReaderFunc)
  44. {
  45. Register(typeReference, newReaderFunc);
  46. GeneratedCodeClass.Methods.Add(newReaderFunc);
  47. }
  48. // Finds existing reader for type, if non exists trys to create one
  49. public MethodReference GetReadFunc(TypeReference variable, ref bool WeavingFailed)
  50. {
  51. if (readFuncs.TryGetValue(variable, out MethodReference foundFunc))
  52. return foundFunc;
  53. TypeReference importedVariable = assembly.MainModule.ImportReference(variable);
  54. return GenerateReader(importedVariable, ref WeavingFailed);
  55. }
  56. MethodReference GenerateReader(TypeReference variableReference, ref bool WeavingFailed)
  57. {
  58. // Arrays are special, if we resolve them, we get the element type,
  59. // so the following ifs might choke on it for scriptable objects
  60. // or other objects that require a custom serializer
  61. // thus check if it is an array and skip all the checks.
  62. if (variableReference.IsArray)
  63. {
  64. if (variableReference.IsMultidimensionalArray())
  65. {
  66. Log.Error($"{variableReference.Name} is an unsupported type. Multidimensional arrays are not supported", variableReference);
  67. WeavingFailed = true;
  68. return null;
  69. }
  70. return GenerateReadCollection(variableReference, variableReference.GetElementType(), nameof(NetworkReaderExtensions.ReadArray), ref WeavingFailed);
  71. }
  72. TypeDefinition variableDefinition = variableReference.Resolve();
  73. // check if the type is completely invalid
  74. if (variableDefinition == null)
  75. {
  76. Log.Error($"{variableReference.Name} is not a supported type", variableReference);
  77. WeavingFailed = true;
  78. return null;
  79. }
  80. else if (variableReference.IsByReference)
  81. {
  82. // error??
  83. Log.Error($"Cannot pass type {variableReference.Name} by reference", variableReference);
  84. WeavingFailed = true;
  85. return null;
  86. }
  87. // use existing func for known types
  88. if (variableDefinition.IsEnum)
  89. {
  90. return GenerateEnumReadFunc(variableReference, ref WeavingFailed);
  91. }
  92. else if (variableDefinition.Is(typeof(ArraySegment<>)))
  93. {
  94. return GenerateArraySegmentReadFunc(variableReference, ref WeavingFailed);
  95. }
  96. else if (variableDefinition.Is(typeof(List<>)))
  97. {
  98. GenericInstanceType genericInstance = (GenericInstanceType)variableReference;
  99. TypeReference elementType = genericInstance.GenericArguments[0];
  100. return GenerateReadCollection(variableReference, elementType, nameof(NetworkReaderExtensions.ReadList), ref WeavingFailed);
  101. }
  102. // handle both NetworkBehaviour and inheritors.
  103. // fixes: https://github.com/MirrorNetworking/Mirror/issues/2939
  104. else if (variableReference.IsDerivedFrom<NetworkBehaviour>() || variableReference.Is<NetworkBehaviour>())
  105. {
  106. return GetNetworkBehaviourReader(variableReference);
  107. }
  108. // check if reader generation is applicable on this type
  109. if (variableDefinition.IsDerivedFrom<UnityEngine.Component>())
  110. {
  111. Log.Error($"Cannot generate reader for component type {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
  112. WeavingFailed = true;
  113. return null;
  114. }
  115. if (variableReference.Is<UnityEngine.Object>())
  116. {
  117. Log.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
  118. WeavingFailed = true;
  119. return null;
  120. }
  121. if (variableReference.Is<UnityEngine.ScriptableObject>())
  122. {
  123. Log.Error($"Cannot generate reader for {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
  124. WeavingFailed = true;
  125. return null;
  126. }
  127. if (variableDefinition.HasGenericParameters)
  128. {
  129. Log.Error($"Cannot generate reader for generic variable {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
  130. WeavingFailed = true;
  131. return null;
  132. }
  133. if (variableDefinition.IsInterface)
  134. {
  135. Log.Error($"Cannot generate reader for interface {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
  136. WeavingFailed = true;
  137. return null;
  138. }
  139. if (variableDefinition.IsAbstract)
  140. {
  141. Log.Error($"Cannot generate reader for abstract class {variableReference.Name}. Use a supported type or provide a custom reader", variableReference);
  142. WeavingFailed = true;
  143. return null;
  144. }
  145. return GenerateClassOrStructReadFunction(variableReference, ref WeavingFailed);
  146. }
  147. MethodReference GetNetworkBehaviourReader(TypeReference variableReference)
  148. {
  149. // uses generic ReadNetworkBehaviour rather than having weaver create one for each NB
  150. MethodReference generic = weaverTypes.readNetworkBehaviourGeneric;
  151. MethodReference readFunc = generic.MakeGeneric(assembly.MainModule, variableReference);
  152. // register function so it is added to Reader<T>
  153. // use Register instead of RegisterWriteFunc because this is not a generated function
  154. Register(variableReference, readFunc);
  155. return readFunc;
  156. }
  157. MethodDefinition GenerateEnumReadFunc(TypeReference variable, ref bool WeavingFailed)
  158. {
  159. MethodDefinition readerFunc = GenerateReaderFunction(variable);
  160. ILProcessor worker = readerFunc.Body.GetILProcessor();
  161. worker.Emit(OpCodes.Ldarg_0);
  162. TypeReference underlyingType = variable.Resolve().GetEnumUnderlyingType();
  163. MethodReference underlyingFunc = GetReadFunc(underlyingType, ref WeavingFailed);
  164. worker.Emit(OpCodes.Call, underlyingFunc);
  165. worker.Emit(OpCodes.Ret);
  166. return readerFunc;
  167. }
  168. MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable, ref bool WeavingFailed)
  169. {
  170. GenericInstanceType genericInstance = (GenericInstanceType)variable;
  171. TypeReference elementType = genericInstance.GenericArguments[0];
  172. MethodDefinition readerFunc = GenerateReaderFunction(variable);
  173. ILProcessor worker = readerFunc.Body.GetILProcessor();
  174. // $array = reader.Read<[T]>()
  175. ArrayType arrayType = elementType.MakeArrayType();
  176. worker.Emit(OpCodes.Ldarg_0);
  177. worker.Emit(OpCodes.Call, GetReadFunc(arrayType, ref WeavingFailed));
  178. // return new ArraySegment<T>($array);
  179. worker.Emit(OpCodes.Newobj, weaverTypes.ArraySegmentConstructorReference.MakeHostInstanceGeneric(assembly.MainModule, genericInstance));
  180. worker.Emit(OpCodes.Ret);
  181. return readerFunc;
  182. }
  183. MethodDefinition GenerateReaderFunction(TypeReference variable)
  184. {
  185. string functionName = $"_Read_{variable.FullName}";
  186. // create new reader for this type
  187. MethodDefinition readerFunc = new MethodDefinition(functionName,
  188. MethodAttributes.Public |
  189. MethodAttributes.Static |
  190. MethodAttributes.HideBySig,
  191. variable);
  192. readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, weaverTypes.Import<NetworkReader>()));
  193. readerFunc.Body.InitLocals = true;
  194. RegisterReadFunc(variable, readerFunc);
  195. return readerFunc;
  196. }
  197. MethodDefinition GenerateReadCollection(TypeReference variable, TypeReference elementType, string readerFunction, ref bool WeavingFailed)
  198. {
  199. MethodDefinition readerFunc = GenerateReaderFunction(variable);
  200. // generate readers for the element
  201. GetReadFunc(elementType, ref WeavingFailed);
  202. ModuleDefinition module = assembly.MainModule;
  203. TypeReference readerExtensions = module.ImportReference(typeof(NetworkReaderExtensions));
  204. MethodReference listReader = Resolvers.ResolveMethod(readerExtensions, assembly, Log, readerFunction, ref WeavingFailed);
  205. GenericInstanceMethod methodRef = new GenericInstanceMethod(listReader);
  206. methodRef.GenericArguments.Add(elementType);
  207. // generates
  208. // return reader.ReadList<T>();
  209. ILProcessor worker = readerFunc.Body.GetILProcessor();
  210. worker.Emit(OpCodes.Ldarg_0); // reader
  211. worker.Emit(OpCodes.Call, methodRef); // Read
  212. worker.Emit(OpCodes.Ret);
  213. return readerFunc;
  214. }
  215. MethodDefinition GenerateClassOrStructReadFunction(TypeReference variable, ref bool WeavingFailed)
  216. {
  217. MethodDefinition readerFunc = GenerateReaderFunction(variable);
  218. // create local for return value
  219. readerFunc.Body.Variables.Add(new VariableDefinition(variable));
  220. ILProcessor worker = readerFunc.Body.GetILProcessor();
  221. TypeDefinition td = variable.Resolve();
  222. if (!td.IsValueType)
  223. GenerateNullCheck(worker, ref WeavingFailed);
  224. CreateNew(variable, worker, td, ref WeavingFailed);
  225. ReadAllFields(variable, worker, ref WeavingFailed);
  226. worker.Emit(OpCodes.Ldloc_0);
  227. worker.Emit(OpCodes.Ret);
  228. return readerFunc;
  229. }
  230. void GenerateNullCheck(ILProcessor worker, ref bool WeavingFailed)
  231. {
  232. // if (!reader.ReadBoolean()) {
  233. // return null;
  234. // }
  235. worker.Emit(OpCodes.Ldarg_0);
  236. worker.Emit(OpCodes.Call, GetReadFunc(weaverTypes.Import<bool>(), ref WeavingFailed));
  237. Instruction labelEmptyArray = worker.Create(OpCodes.Nop);
  238. worker.Emit(OpCodes.Brtrue, labelEmptyArray);
  239. // return null
  240. worker.Emit(OpCodes.Ldnull);
  241. worker.Emit(OpCodes.Ret);
  242. worker.Append(labelEmptyArray);
  243. }
  244. // Initialize the local variable with a new instance
  245. void CreateNew(TypeReference variable, ILProcessor worker, TypeDefinition td, ref bool WeavingFailed)
  246. {
  247. if (variable.IsValueType)
  248. {
  249. // structs are created with Initobj
  250. worker.Emit(OpCodes.Ldloca, 0);
  251. worker.Emit(OpCodes.Initobj, variable);
  252. }
  253. else if (td.IsDerivedFrom<UnityEngine.ScriptableObject>())
  254. {
  255. GenericInstanceMethod genericInstanceMethod = new GenericInstanceMethod(weaverTypes.ScriptableObjectCreateInstanceMethod);
  256. genericInstanceMethod.GenericArguments.Add(variable);
  257. worker.Emit(OpCodes.Call, genericInstanceMethod);
  258. worker.Emit(OpCodes.Stloc_0);
  259. }
  260. else
  261. {
  262. // classes are created with their constructor
  263. MethodDefinition ctor = Resolvers.ResolveDefaultPublicCtor(variable);
  264. if (ctor == null)
  265. {
  266. Log.Error($"{variable.Name} can't be deserialized because it has no default constructor. Don't use {variable.Name} in [SyncVar]s, Rpcs, Cmds, etc.", variable);
  267. WeavingFailed = true;
  268. return;
  269. }
  270. MethodReference ctorRef = assembly.MainModule.ImportReference(ctor);
  271. worker.Emit(OpCodes.Newobj, ctorRef);
  272. worker.Emit(OpCodes.Stloc_0);
  273. }
  274. }
  275. void ReadAllFields(TypeReference variable, ILProcessor worker, ref bool WeavingFailed)
  276. {
  277. foreach (FieldDefinition field in variable.FindAllPublicFields())
  278. {
  279. // mismatched ldloca/ldloc for struct/class combinations is invalid IL, which causes crash at runtime
  280. OpCode opcode = variable.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc;
  281. worker.Emit(opcode, 0);
  282. MethodReference readFunc = GetReadFunc(field.FieldType, ref WeavingFailed);
  283. if (readFunc != null)
  284. {
  285. worker.Emit(OpCodes.Ldarg_0);
  286. worker.Emit(OpCodes.Call, readFunc);
  287. }
  288. else
  289. {
  290. Log.Error($"{field.Name} has an unsupported type", field);
  291. WeavingFailed = true;
  292. }
  293. FieldReference fieldRef = assembly.MainModule.ImportReference(field);
  294. worker.Emit(OpCodes.Stfld, fieldRef);
  295. }
  296. }
  297. // Save a delegate for each one of the readers into Reader<T>.read
  298. internal void InitializeReaders(ILProcessor worker)
  299. {
  300. ModuleDefinition module = assembly.MainModule;
  301. TypeReference genericReaderClassRef = module.ImportReference(typeof(Reader<>));
  302. System.Reflection.FieldInfo fieldInfo = typeof(Reader<>).GetField(nameof(Reader<object>.read));
  303. FieldReference fieldRef = module.ImportReference(fieldInfo);
  304. TypeReference networkReaderRef = module.ImportReference(typeof(NetworkReader));
  305. TypeReference funcRef = module.ImportReference(typeof(Func<,>));
  306. MethodReference funcConstructorRef = module.ImportReference(typeof(Func<,>).GetConstructors()[0]);
  307. foreach (KeyValuePair<TypeReference, MethodReference> kvp in readFuncs)
  308. {
  309. TypeReference targetType = kvp.Key;
  310. MethodReference readFunc = kvp.Value;
  311. // create a Func<NetworkReader, T> delegate
  312. worker.Emit(OpCodes.Ldnull);
  313. worker.Emit(OpCodes.Ldftn, readFunc);
  314. GenericInstanceType funcGenericInstance = funcRef.MakeGenericInstanceType(networkReaderRef, targetType);
  315. MethodReference funcConstructorInstance = funcConstructorRef.MakeHostInstanceGeneric(assembly.MainModule, funcGenericInstance);
  316. worker.Emit(OpCodes.Newobj, funcConstructorInstance);
  317. // save it in Reader<T>.read
  318. GenericInstanceType genericInstance = genericReaderClassRef.MakeGenericInstanceType(targetType);
  319. FieldReference specializedField = fieldRef.SpecializeField(assembly.MainModule, genericInstance);
  320. worker.Emit(OpCodes.Stsfld, specializedField);
  321. }
  322. }
  323. }
  324. }