FixHelper.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using System.Linq;
  5. using System.IO;
  6. using System.Text;
  7. namespace com.bbbirder.injection
  8. {
  9. public static class FixHelper
  10. {
  11. public static HashSet<string> fixedAssemblies = new();
  12. /// <summary>
  13. /// Fix all injections in all assemblies in current domain
  14. /// </summary>
  15. public static void InstallAll()
  16. {
  17. var allAssemblies = AppDomain.CurrentDomain.GetAssemblies();
  18. foreach (var assembly in allAssemblies)
  19. {
  20. Install(assembly);
  21. }
  22. DebugHelper.Log($"fixed {allInjections.Length} injections successfully!");
  23. }
  24. /// <summary>
  25. /// Fix injections who are provided from the specific assembly
  26. /// </summary>
  27. /// <remarks>
  28. /// <para>
  29. /// Be aware that the injections may refer to another assembly
  30. /// </para>
  31. /// </remarks>
  32. /// <param name="assembly"></param>
  33. public static void Install(Assembly assembly)
  34. {
  35. var assemblyName = assembly.GetName().Name;
  36. if (fixedAssemblies.Contains(assemblyName)) return;
  37. foreach (var injection in GetAllInjections(new[] { assembly }))
  38. {
  39. FixMethod(injection);
  40. DryInject(injection);
  41. }
  42. fixedAssemblies.Add(assemblyName);
  43. }
  44. /// <summary>
  45. /// check if the assembly of target type is injected
  46. /// </summary>
  47. /// <param name="type"></param>
  48. /// <returns></returns>
  49. public static bool IsInjected(Type type)
  50. {
  51. var mark = type.Assembly.GetType($"{Constants.INJECTED_MARK_NAMESPACE}.{Constants.INJECTED_MARK_NAME}");
  52. return mark != null;
  53. }
  54. public static MethodInfo GetOriginMethodFor(MethodInfo targetMethod)
  55. {
  56. var oriName = Constants.GetOriginMethodName(targetMethod.Name, targetMethod.GetSignature());
  57. return targetMethod.DeclaringType.GetMethod(oriName, bindingFlags);
  58. }
  59. public static void DryInject(InjectionInfo injection)
  60. {
  61. injection.onStartFix?.Invoke();
  62. var injectMethod = injection.DryInjectMethod;
  63. if (injectMethod == null) return;
  64. var targetType = injection.DryInjectAssmemble;
  65. var sfldName = injection.DryInjectMethodName;
  66. // set static field value
  67. FieldInfo sfld;
  68. sfld = targetType.GetField(sfldName, bindingFlags ^ BindingFlags.Instance);
  69. var @delegate = injectMethod.CreateDelegate(sfld.FieldType);
  70. var combined = Delegate.Combine(sfld.GetValue(null) as Delegate, @delegate);
  71. sfld.SetValue(null, combined);
  72. }
  73. public static void FixMethod(InjectionInfo injection)
  74. {
  75. injection.onStartFix?.Invoke();
  76. var targetMethod = injection.InjectedMethod;
  77. var fixingMethod = injection.FixingMethod;
  78. var fixingDelegate = injection.FixingDelegate;
  79. if ((targetMethod ?? fixingMethod ?? (object)fixingDelegate) == null)
  80. {
  81. return;
  82. }
  83. var targetType = targetMethod.DeclaringType;
  84. var methodName = targetMethod.Name;
  85. // set static field value
  86. FieldInfo sfld;
  87. try
  88. {
  89. var sfldName = Constants.GetInjectedFieldName(methodName, targetMethod.GetSignature());
  90. sfld = targetType.GetField(sfldName, bindingFlags ^ BindingFlags.Instance);
  91. }
  92. catch (Exception e)
  93. {
  94. var msg = $"error on set fixing field {methodName} on {targetType}\n{e.Message}\n{e.StackTrace}";
  95. DebugHelper.LogError(msg);
  96. throw;
  97. }
  98. try
  99. {
  100. if (sfld is null)
  101. {
  102. throw new($"Unable to fix target method {methodName} in type {targetType}, this may caused by injection issues."
  103. + " Try to inject manually in [Tools/bbbirder/Unity Injection] if you see this in Editor mode");
  104. }
  105. var @delegate = fixingDelegate ?? fixingMethod.CreateDelegate(sfld.FieldType);
  106. if (@delegate is null)
  107. {
  108. throw new($"Unable to create delegate for replace method {fixingMethod}, whose target is {methodName}");
  109. }
  110. var combined = Delegate.Combine(sfld.GetValue(null) as Delegate, @delegate);
  111. sfld.SetValue(null, combined);
  112. }
  113. catch (Exception e)
  114. {
  115. var msg = $"error on create and set delegate for injection method {methodName}\n{e.Message}\n{e.StackTrace}";
  116. DebugHelper.LogError(msg);
  117. throw;
  118. }
  119. // set overwrite origin field
  120. var originName = Constants.GetOriginMethodName(methodName, targetMethod.GetSignature());
  121. var originMethod = targetType.GetMethod(originName, bindingFlags);
  122. try
  123. {
  124. var oriDelegate = originMethod.CreateDelegate(sfld.FieldType);
  125. if (oriDelegate is null)
  126. {
  127. throw new($"create original delegate for {methodName} failed");
  128. }
  129. injection.OriginReceiver?.Invoke(oriDelegate);
  130. }
  131. catch (Exception e)
  132. {
  133. var msg = $"error on create and set delegate for original method {methodName}\n{e.Message}\n{e.StackTrace}";
  134. DebugHelper.LogError(msg);
  135. throw;
  136. }
  137. }
  138. /// <summary>
  139. /// Get all injections in current domain.
  140. /// </summary>
  141. /// <param name="assemblies">The assemblies to search in. All loaded assemblies if omitted</param>
  142. /// <returns></returns>
  143. public static InjectionInfo[] GetAllInjections(Assembly[] assemblies = null)
  144. {
  145. assemblies ??= AppDomain.CurrentDomain.GetAssemblies();
  146. var injections = assemblies
  147. // .Where(a=>a.MayContainsInjection())
  148. .SelectMany(a => Retriever.GetAllAttributes<InjectionAttribute>(a))
  149. .SelectMany(attr => attr.ProvideInjections())
  150. ;
  151. var injections2 = assemblies
  152. .SelectMany(a => Retriever.GetAllSubtypes<IInjection>(a))
  153. .Where(type => !type.IsInterface && !type.IsAbstract)
  154. .Select(type => System.Activator.CreateInstance(type) as IInjection)
  155. .SelectMany(ii => ii.ProvideInjections())
  156. ;
  157. return injections.Concat(injections2).ToArray();
  158. }
  159. public static string[] GetAllInjectionSources()
  160. {
  161. var attributeSources = Retriever.GetAllAttributes<InjectionAttribute>()
  162. .Select(attr => attr.targetInfo.Module.Assembly);
  163. var subtypeSources = Retriever.GetAllSubtypes<IInjection>()
  164. .Select(t => t.Assembly);
  165. return attributeSources.Concat(subtypeSources)
  166. .Distinct()
  167. .Select(ass => ass.GetAssemblyPath())
  168. .Distinct()
  169. .ToArray();
  170. }
  171. public static string GetAssemblyPath(this Assembly assembly)
  172. {
  173. if (assembly == null)
  174. {
  175. return null;
  176. }
  177. if (assembly.IsDynamic)
  178. {
  179. return null;
  180. }
  181. if (assembly.CodeBase == null)
  182. {
  183. return null;
  184. }
  185. string text = "file:///";
  186. string codeBase = assembly.CodeBase;
  187. if (codeBase.StartsWith(text, StringComparison.InvariantCultureIgnoreCase))
  188. {
  189. codeBase = codeBase.Substring(text.Length);
  190. codeBase = codeBase.Replace('\\', '/');
  191. if (File.Exists(codeBase))
  192. {
  193. return codeBase;
  194. }
  195. if (!Path.IsPathRooted(codeBase))
  196. {
  197. codeBase = (!File.Exists("/" + codeBase)) ? Path.GetFullPath(codeBase) : ("/" + codeBase);
  198. }
  199. if (File.Exists(codeBase))
  200. {
  201. return codeBase;
  202. }
  203. try
  204. {
  205. codeBase = assembly.Location;
  206. }
  207. catch
  208. {
  209. return null;
  210. }
  211. if (File.Exists(codeBase))
  212. {
  213. return codeBase;
  214. }
  215. }
  216. if (File.Exists(assembly.Location))
  217. {
  218. return assembly.Location;
  219. }
  220. return null;
  221. }
  222. public static string GetSignature(this MethodBase method)
  223. {
  224. var builder = new StringBuilder();
  225. builder.Append(method.Name);
  226. if (method.IsGenericMethod)
  227. {
  228. builder.Append('`');
  229. builder.Append(method.GetGenericArguments().Length);
  230. }
  231. builder.Append('(');
  232. var parameters = method.GetParameters();
  233. for (int i = 0; i < parameters.Length; i++)
  234. {
  235. var parameterDefinition = parameters[i];
  236. if (i > 0)
  237. {
  238. builder.Append(",");
  239. }
  240. AppendTypeFullName(builder, parameterDefinition.ParameterType);
  241. }
  242. builder.Append(')');
  243. return builder.ToString();
  244. static void AppendTypeFullName(StringBuilder builder, Type type)
  245. {
  246. if (!string.IsNullOrEmpty(type.Namespace))
  247. {
  248. builder.Append(type.Namespace);
  249. builder.Append("::");
  250. }
  251. var stack = new Stack<Type>();
  252. var declaringType = type;
  253. while (null != declaringType)
  254. {
  255. stack.Push(declaringType);
  256. declaringType = declaringType.DeclaringType;
  257. }
  258. while (stack.TryPop(out var name)) AppendNestedType(builder, name);
  259. }
  260. static void AppendNestedType(StringBuilder builder, Type type)
  261. {
  262. builder.Append(type.Name);
  263. if (type.IsGenericType)
  264. {
  265. builder.Append('<');
  266. var args = type.GenericTypeArguments;
  267. for (int i = 0; i < args.Length; i++)
  268. {
  269. if (i != 0)
  270. {
  271. builder.Append(',');
  272. }
  273. AppendTypeFullName(builder, args[i]);
  274. }
  275. builder.Append('>');
  276. }
  277. }
  278. }
  279. static InjectionInfo[] m_allInjections;
  280. public static InjectionInfo[] allInjections => m_allInjections ??= GetAllInjections();
  281. static BindingFlags bindingFlags = 0
  282. | BindingFlags.Static
  283. | BindingFlags.Instance
  284. | BindingFlags.Public
  285. | BindingFlags.NonPublic
  286. ;
  287. }
  288. }