diff --git a/IPA.Injector/Injector.cs b/IPA.Injector/Injector.cs index f62b263e..1565770b 100644 --- a/IPA.Injector/Injector.cs +++ b/IPA.Injector/Injector.cs @@ -137,6 +137,11 @@ namespace IPA.Injector Loader.LibLoader.Configure(); } + private static void InstallHarmonyProtections() + { // proxy function to delay resolution + HarmonyProtectorProxy.ProtectNull(); + } + private static void InstallBootstrapPatch() { var sw = Stopwatch.StartNew(); @@ -312,6 +317,8 @@ namespace IPA.Injector // need to reinit streams singe Unity seems to redirect stdout StdoutInterceptor.RedirectConsole(); + InstallHarmonyProtections(); + var bootstrapper = new GameObject("NonDestructiveBootstrapper").AddComponent(); bootstrapper.Destroyed += Bootstrapper_Destroyed; } diff --git a/IPA.Loader/Loader/HarmonyProtector.cs b/IPA.Loader/Loader/HarmonyProtector.cs new file mode 100644 index 00000000..549b74f1 --- /dev/null +++ b/IPA.Loader/Loader/HarmonyProtector.cs @@ -0,0 +1,79 @@ +using HarmonyLib; +using IPA.Logging; +using MonoMod.RuntimeDetour; +using System; +using System.Reflection; + +namespace IPA.Loader +{ + internal static class HarmonyProtectorProxy + { + public static void ProtectNull() => HarmonyProtector.Protect(); + } + + internal static class HarmonyProtector + { + public static void Protect() + { + var guid = Guid.NewGuid().ToString("N"); + var id = guid.Remove(new Random().Next(7, guid.Length - 1)); + var harmony = new Harmony(id); + + var unpatchByTypeOrId = AccessTools.Method(typeof(PatchProcessor), nameof(PatchProcessor.Unpatch), new[] { typeof(HarmonyPatchType), typeof(string) }); + var unpatchMethod = AccessTools.Method(typeof(PatchProcessor), nameof(PatchProcessor.Unpatch), new[] { typeof(MethodInfo) }); + var processPatchJob = AccessTools.Method(typeof(PatchClassProcessor), "ProcessPatchJob"); + var patch = AccessTools.Method(typeof(PatchProcessor), nameof(PatchProcessor.Patch)); + + var unpatchPrefix = AccessTools.Method(typeof(HarmonyProtector), nameof(PatchProcessor_Unpatch_Prefix)); + var processPatchJobPrefix = AccessTools.Method(typeof(HarmonyProtector), nameof(PatchClassProcessor_ProcessPatchJob_Prefix)); + var patchPrefix = AccessTools.Method(typeof(HarmonyProtector), nameof(PatchProcessor_Patch_Prefix)); + + harmony.Patch(unpatchByTypeOrId, new HarmonyMethod(unpatchPrefix)); + harmony.Patch(unpatchMethod, new HarmonyMethod(unpatchPrefix)); + harmony.Patch(processPatchJob, new HarmonyMethod(processPatchJobPrefix)); + harmony.Patch(patch, new HarmonyMethod(patchPrefix)); + } + + private static bool ShouldBlockExecution(MethodBase methodBase) + { + var getIdentifiable = AccessTools.Method(typeof(DetourHelper), nameof(DetourHelper.GetIdentifiable)).GetIdentifiable(); + var getValue = AccessTools.Method(typeof(FieldInfo), nameof(FieldInfo.GetValue)).GetIdentifiable(); + var declaringTypeGetter = AccessTools.PropertyGetter(typeof(MemberInfo), nameof(MemberInfo.DeclaringType)).GetIdentifiable(); + var methodBaseEquals = AccessTools.Method(typeof(MethodBase), nameof(MethodBase.Equals)).GetIdentifiable(); + var assemblyEquals = AccessTools.Method(typeof(Assembly), nameof(Assembly.Equals)).GetIdentifiable(); + var assemblyGetter = AccessTools.PropertyGetter(typeof(Type), nameof(Type.Assembly)).GetIdentifiable(); + var getExecutingAssembly = AccessTools.Method(typeof(Assembly), nameof(Assembly.GetExecutingAssembly)).GetIdentifiable(); + var method = methodBase.GetIdentifiable(); + var assembly = method.DeclaringType!.Assembly; + return method.Equals(getIdentifiable) || + method.Equals(getValue) || + method.Equals(declaringTypeGetter) || + method.Equals(methodBaseEquals) || + method.Equals(assemblyEquals) || + method.Equals(assemblyGetter) || + method.Equals(getExecutingAssembly) || + assembly.Equals(Assembly.GetExecutingAssembly()) || + assembly.Equals(typeof(Harmony).Assembly); + } + + private static bool PatchProcessor_Patch_Prefix(MethodBase ___original, ref MethodInfo __result) + { + if (ShouldBlockExecution(___original)) + { + __result = ___original as MethodInfo; + return false; + } + + return true; + } + + private static bool PatchClassProcessor_ProcessPatchJob_Prefix(object job) + { + var original = AccessTools.Field(job.GetType(), "original"); + var methodBase = (MethodBase)original!.GetValue(job); + return !ShouldBlockExecution(methodBase); + } + + private static bool PatchProcessor_Unpatch_Prefix(MethodBase ___original) => !ShouldBlockExecution(___original); + } +}