HarmonyInstanceExtensions.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using System.Text;
  6. using UnityEngine;
  7. using XUnity.AutoTranslator.Plugin.Core.Configuration;
  8. using XUnity.AutoTranslator.Plugin.Core.Constants;
  9. using XUnity.AutoTranslator.Plugin.Core.Hooks;
  10. using XUnity.AutoTranslator.Plugin.Core.Utilities;
  11. using XUnity.RuntimeHooker;
  12. using XUnity.RuntimeHooker.Core;
  13. namespace XUnity.AutoTranslator.Plugin.Core.Extensions
  14. {
  15. internal static class HarmonyInstanceExtensions
  16. {
  17. public static readonly MethodInfo PatchMethod12 = ClrTypes.HarmonyInstance?.GetMethod( "Patch", new Type[] { ClrTypes.MethodBase, ClrTypes.HarmonyMethod, ClrTypes.HarmonyMethod, ClrTypes.HarmonyMethod } );
  18. public static readonly MethodInfo PatchMethod20 = ClrTypes.Harmony?.GetMethod( "Patch", new Type[] { ClrTypes.MethodBase, ClrTypes.HarmonyMethod, ClrTypes.HarmonyMethod, ClrTypes.HarmonyMethod, ClrTypes.HarmonyMethod } );
  19. public static void PatchAll( this object instance, IEnumerable<Type> types )
  20. {
  21. foreach( var type in types )
  22. {
  23. instance.PatchType( type );
  24. }
  25. }
  26. private static object CreateHarmonyMethod( MethodInfo method, int? priority )
  27. {
  28. var harmonyMethod = ClrTypes.HarmonyMethod.GetConstructor( new Type[] { typeof( MethodInfo ) } )
  29. .Invoke( new object[] { method } );
  30. if( priority.HasValue )
  31. {
  32. var field = ClrTypes.HarmonyMethod.GetField( "priority", BindingFlags.Public | BindingFlags.Instance )
  33. ?? ClrTypes.HarmonyMethod.GetField( "prioritiy", BindingFlags.Public | BindingFlags.Instance );
  34. field.SetValue( harmonyMethod, priority.Value );
  35. }
  36. return harmonyMethod;
  37. }
  38. public static void PatchType( this object instance, Type type )
  39. {
  40. MethodBase original = null;
  41. try
  42. {
  43. var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
  44. var prepare = type.GetMethod( "Prepare", flags );
  45. if( prepare == null || (bool)prepare.Invoke( null, new object[] { instance } ) )
  46. {
  47. original = (MethodBase)type.GetMethod( "TargetMethod", flags ).Invoke( null, new object[] { instance } );
  48. if( original != null )
  49. {
  50. var requireRuntimeHooker = (bool?)type.GetProperty( "RequireRuntimeHooker", flags )?.GetValue( null, null ) == true;
  51. var priority = type.GetCustomAttributes( typeof( HarmonyPriorityShimAttribute ), false )
  52. .OfType<HarmonyPriorityShimAttribute>()
  53. .FirstOrDefault()
  54. ?.priority;
  55. var prefix = type.GetMethod( "Prefix", flags );
  56. var postfix = type.GetMethod( "Postfix", flags );
  57. var transpiler = type.GetMethod( "Transpiler", flags );
  58. var harmonyPrefix = prefix != null ? CreateHarmonyMethod( prefix, priority ) : null;
  59. var harmonyPostfix = postfix != null ? CreateHarmonyMethod( postfix, priority ) : null;
  60. var harmonyTranspiler = transpiler != null ? CreateHarmonyMethod( transpiler, priority ) : null;
  61. if( requireRuntimeHooker || Settings.ForceExperimentalHooks )
  62. {
  63. if( Settings.EnableExperimentalHooks )
  64. {
  65. XuaLogger.Current.Info( $"Hooking '{original.DeclaringType.FullName}.{original.Name}' through experimental hooks." );
  66. var hookPrefix = harmonyPrefix != null ? new HookMethod( prefix, priority ?? -1 ) : null;
  67. var hookPostfix = harmonyPostfix != null ? new HookMethod( postfix, priority ?? -1 ) : null;
  68. RuntimeMethodPatcher.Patch( original, hookPrefix, hookPostfix );
  69. }
  70. else
  71. {
  72. XuaLogger.Current.Info( $"Skipping hook on '{original.DeclaringType.FullName}.{original.Name}' because it requires 'EnableExperimentalHooks' enabled is config file." );
  73. }
  74. }
  75. else
  76. {
  77. try
  78. {
  79. if( PatchMethod12 != null )
  80. {
  81. PatchMethod12.Invoke( instance, new object[] { original, harmonyPrefix, harmonyPostfix, harmonyTranspiler } );
  82. }
  83. else
  84. {
  85. PatchMethod20.Invoke( instance, new object[] { original, harmonyPrefix, harmonyPostfix, harmonyTranspiler, null } );
  86. }
  87. }
  88. catch( Exception e ) when( e.IsCausedBy<PlatformNotSupportedException>() )
  89. {
  90. if( Settings.EnableExperimentalHooks )
  91. {
  92. XuaLogger.Current.Info( $"Harmony is not supported in this runtime. Hooking '{original.DeclaringType.FullName}.{original.Name}' through experimental hooks." );
  93. var hookPrefix = harmonyPrefix != null ? new HookMethod( prefix, priority ?? -1 ) : null;
  94. var hookPostfix = harmonyPostfix != null ? new HookMethod( postfix, priority ?? -1 ) : null;
  95. RuntimeMethodPatcher.Patch( original, hookPrefix, hookPostfix );
  96. }
  97. else
  98. {
  99. XuaLogger.Current.Error( $"Cannot hook '{original.DeclaringType.FullName}.{original.Name}'. Harmony is not supported in this runtime. You can try to 'EnableExperimentalHooks' in the config file to enable support for this game." );
  100. }
  101. }
  102. }
  103. }
  104. else
  105. {
  106. XuaLogger.Current.Warn( $"Could not enable hook '{type.Name}'. Likely due differences between different versions of the engine or text framework." );
  107. }
  108. }
  109. }
  110. catch( Exception e )
  111. {
  112. if( original != null )
  113. {
  114. XuaLogger.Current.Warn( e, $"An error occurred while patching property/method '{original.DeclaringType.FullName}.{original.Name}'." );
  115. }
  116. else
  117. {
  118. XuaLogger.Current.Warn( e, $"An error occurred while patching property/method. Failing hook: '{type.Name}'." );
  119. }
  120. }
  121. }
  122. }
  123. }