using System; using System.Collections.Generic; using System.Runtime.InteropServices; using MonoMod.RuntimeDetour; using MonoMod.Utils; using UnityEditor; using UnityEngine; namespace MeatKit { /// /// This class helps manage detours into the native code of the Editor. /// Any detours into native code should be registered using this as it will automatically dispose of them /// right before the Editor reloads the mono domain, preventing editor crashes. /// [InitializeOnLoad] public static class NativeHookManager { // Actual name: ShutdownPlatformSupportModulesInManaged(void) private delegate void ShutdownManaged(); private static readonly ShutdownManaged OrigShutdownManaged; // Keep track of all the applied detours so we can quickly undo them before the mono domain is reloaded private static readonly List Detours = new List(); static NativeHookManager() { if (!EditorVersion.IsSupportedVersion) return; // Apply our detours here and save the trampoline to call the original function OrigShutdownManaged = ApplyEditorDetour(EditorVersion.Current.FunctionOffsets.ShutdownManaged, new ShutdownManaged(OnShutdownManaged)); } public static T ApplyEditorDetour(long from, Delegate to) where T : class { // Avoid crashing the editor if we're loaded in the wrong Unity version if (!EditorVersion.IsSupportedVersion) return null; // Get the base address of the Unity module and the address in memory of the function IntPtr editorBase = DynDll.OpenLibrary("Unity.exe"); IntPtr fromPtr = (IntPtr)(editorBase.ToInt64() + from); // Get a function pointer for the managed callback var toPtr = Marshal.GetFunctionPointerForDelegate(to); // Make a detour and add it to the list var detour = new NativeDetour(fromPtr, toPtr, new NativeDetourConfig { ManualApply = true }); Detours.Add(detour); // Apply the detour and generate a trampoline for it, which we return var original = detour.GenerateTrampoline(to.GetType().GetMethod("Invoke")).CreateDelegate(typeof(T)) as T; detour.Apply(); return original; } public static Delegate GetDelegateForFunctionPointer(long from) { // Avoid crashing the editor if we're loaded in the wrong Unity version if (!EditorVersion.IsSupportedVersion) return null; // Get the base address for the Unity module and apply the offset IntPtr editorBase = DynDll.OpenLibrary("Unity.exe"); return Marshal.GetDelegateForFunctionPointer((IntPtr)(editorBase.ToInt64() + from), typeof(T)); } private static void OnShutdownManaged() { // Unity is about to shutdown the mono runtime! Quickly dispose of our detours! OrigShutdownManaged(); foreach (var detour in Detours) detour.Dispose(); } } }