From f900cce447ba5be752d141884a14692dd69a7a16 Mon Sep 17 00:00:00 2001 From: Herp Derpinstine Date: Mon, 20 Nov 2023 12:05:02 -0600 Subject: [PATCH] More Work on Unity Support Module --- .../MelonLoader.Bootstrap/BootstrapInterop.cs | 11 +- .../MelonLoaderInvoker.cs | 8 +- .../NativeEntryPoint.cs | 1 - MelonLoader/MelonLoader.NativeHost/Stereo.cs | 51 +++--- MelonLoader/MelonLoader.Shared/Core.cs | 4 +- .../Utils/MelonExtensions.cs | 33 ++++ .../Unity/Unity.Bootstrap/Bootstrap.cs | 27 ++- .../Modules/Unity/Unity.Bootstrap/Il2Cpp.cs | 16 ++ .../Modules/Unity/Unity.Bootstrap/Mono.cs | 155 ++++++++++++++++++ 9 files changed, 265 insertions(+), 41 deletions(-) create mode 100644 MelonLoader/MelonLoader.Shared/Utils/MelonExtensions.cs create mode 100644 MelonLoader/Modules/Unity/Unity.Bootstrap/Il2Cpp.cs create mode 100644 MelonLoader/Modules/Unity/Unity.Bootstrap/Mono.cs diff --git a/MelonLoader/MelonLoader.Bootstrap/BootstrapInterop.cs b/MelonLoader/MelonLoader.Bootstrap/BootstrapInterop.cs index 4a0119a2f..9e54a8843 100644 --- a/MelonLoader/MelonLoader.Bootstrap/BootstrapInterop.cs +++ b/MelonLoader/MelonLoader.Bootstrap/BootstrapInterop.cs @@ -1,8 +1,15 @@ +using System; + namespace MelonLoader.Bootstrap { public static unsafe class BootstrapInterop { - public static delegate* unmanaged HookAttach; - public static delegate* unmanaged HookDetach; + public static delegate* unmanaged NativeHookAttach; + public static delegate* unmanaged NativeHookDetach; + + public static void HookAttach(IntPtr target, IntPtr detour) + => NativeHookAttach(target.ToPointer(), detour.ToPointer()); + public static void HookDetach(IntPtr target) + => NativeHookDetach(target.ToPointer()); } } \ No newline at end of file diff --git a/MelonLoader/MelonLoader.NativeHost/MelonLoaderInvoker.cs b/MelonLoader/MelonLoader.NativeHost/MelonLoaderInvoker.cs index bffa1c54c..ef58ea374 100644 --- a/MelonLoader/MelonLoader.NativeHost/MelonLoaderInvoker.cs +++ b/MelonLoader/MelonLoader.NativeHost/MelonLoaderInvoker.cs @@ -6,10 +6,10 @@ internal class MelonLoaderInvoker { internal static unsafe void Initialize() { - BootstrapInterop.HookAttach = NativeEntryPoint.Exports.HookAttach; - BootstrapInterop.HookDetach = NativeEntryPoint.Exports.HookDetach; - - MelonLoader.Bootstrap.Entrypoint.Entry(); + BootstrapInterop.NativeHookAttach = NativeEntryPoint.Exports.HookAttach; + BootstrapInterop.NativeHookDetach = NativeEntryPoint.Exports.HookDetach; + + Entrypoint.Entry(); } } } \ No newline at end of file diff --git a/MelonLoader/MelonLoader.NativeHost/NativeEntryPoint.cs b/MelonLoader/MelonLoader.NativeHost/NativeEntryPoint.cs index b7d398297..257935e37 100644 --- a/MelonLoader/MelonLoader.NativeHost/NativeEntryPoint.cs +++ b/MelonLoader/MelonLoader.NativeHost/NativeEntryPoint.cs @@ -16,7 +16,6 @@ static unsafe void LoadStage1(HostImports* imports) imports->LoadAssemblyAndGetPtr = &Stereo.LoadAssemblyAndGetFuncPtr; } - [UnmanagedCallersOnly] static unsafe void LoadStage2(HostImports* imports, HostExports* exports) { diff --git a/MelonLoader/MelonLoader.NativeHost/Stereo.cs b/MelonLoader/MelonLoader.NativeHost/Stereo.cs index ff98f6411..778ca0c74 100644 --- a/MelonLoader/MelonLoader.NativeHost/Stereo.cs +++ b/MelonLoader/MelonLoader.NativeHost/Stereo.cs @@ -3,38 +3,39 @@ using System.Runtime.InteropServices; using System.Runtime.Loader; -namespace MelonLoader.NativeHost; - -public static class Stereo +namespace MelonLoader.NativeHost { - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] - internal static unsafe void LoadAssemblyAndGetFuncPtr(IntPtr pathNative, IntPtr typeNameNative, IntPtr methodNameNative, void** resultHandle) + public static class Stereo { - var assemblyPath = Marshal.PtrToStringUni(pathNative); - var typeName = Marshal.PtrToStringUni(typeNameNative); - var methodName = Marshal.PtrToStringUni(methodNameNative); - - ArgumentNullException.ThrowIfNull(assemblyPath); - ArgumentNullException.ThrowIfNull(typeName); - ArgumentNullException.ThrowIfNull(methodName); + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] + internal static unsafe void LoadAssemblyAndGetFuncPtr(IntPtr pathNative, IntPtr typeNameNative, IntPtr methodNameNative, void** resultHandle) + { + var assemblyPath = Marshal.PtrToStringUni(pathNative); + var typeName = Marshal.PtrToStringUni(typeNameNative); + var methodName = Marshal.PtrToStringUni(methodNameNative); + + ArgumentNullException.ThrowIfNull(assemblyPath); + ArgumentNullException.ThrowIfNull(typeName); + ArgumentNullException.ThrowIfNull(methodName); + + if ((IntPtr)resultHandle == IntPtr.Zero) + throw new ArgumentNullException(nameof(resultHandle)); + + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); - if ((IntPtr)resultHandle == IntPtr.Zero) - throw new ArgumentNullException(nameof(resultHandle)); + Func resolver = name => AssemblyLoadContext.Default.LoadFromAssemblyName(name); - var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); + var type = Type.GetType(typeName, resolver, null, true); - Func resolver = name => AssemblyLoadContext.Default.LoadFromAssemblyName(name); + if (type == null) + throw new TypeLoadException("Failed to load type: " + typeName); - var type = Type.GetType(typeName, resolver, null, true); - - if(type == null) - throw new TypeLoadException("Failed to load type: " + typeName); - - var method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + var method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); - if (method == null) - throw new MissingMethodException(typeName, methodName); + if (method == null) + throw new MissingMethodException(typeName, methodName); - *resultHandle = (void*)method.MethodHandle.GetFunctionPointer(); + *resultHandle = (void*)method.MethodHandle.GetFunctionPointer(); + } } } \ No newline at end of file diff --git a/MelonLoader/MelonLoader.Shared/Core.cs b/MelonLoader/MelonLoader.Shared/Core.cs index c13060b68..92cab7e8e 100644 --- a/MelonLoader/MelonLoader.Shared/Core.cs +++ b/MelonLoader/MelonLoader.Shared/Core.cs @@ -1,6 +1,4 @@ -using System.IO; -using System.Reflection; -using MelonLoader.Shared.Utils; +using MelonLoader.Shared.Utils; namespace MelonLoader.Shared { diff --git a/MelonLoader/MelonLoader.Shared/Utils/MelonExtensions.cs b/MelonLoader/MelonLoader.Shared/Utils/MelonExtensions.cs new file mode 100644 index 000000000..5e48db266 --- /dev/null +++ b/MelonLoader/MelonLoader.Shared/Utils/MelonExtensions.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.InteropServices; + +namespace MelonLoader.Shared.Utils +{ + public static class MelonExtensions + { + #region Delegate + + public static IntPtr GetFunctionPointer(this Delegate del) + => Marshal.GetFunctionPointerForDelegate(del); + + #endregion + + #region IntPtr + + public static void GetDelegate(this IntPtr ptr, out T output) where T : Delegate + => output = GetDelegate(ptr); + public static T GetDelegate(this IntPtr ptr) where T : Delegate + => GetDelegate(ptr, typeof(T)) as T; + public static Delegate GetDelegate(this IntPtr ptr, Type type) + { + if (ptr == IntPtr.Zero) + throw new ArgumentNullException(nameof(ptr)); + Delegate del = Marshal.GetDelegateForFunctionPointer(ptr, type); + if (del == null) + throw new Exception($"Unable to Get Delegate of Type {type.FullName} for Function Pointer!"); + return del; + } + + #endregion + } +} diff --git a/MelonLoader/Modules/Unity/Unity.Bootstrap/Bootstrap.cs b/MelonLoader/Modules/Unity/Unity.Bootstrap/Bootstrap.cs index dc9708ae8..04cd199d5 100644 --- a/MelonLoader/Modules/Unity/Unity.Bootstrap/Bootstrap.cs +++ b/MelonLoader/Modules/Unity/Unity.Bootstrap/Bootstrap.cs @@ -15,19 +15,34 @@ public bool IsMyEngine { get { - string appData = $"{Path.Combine(MelonEnvironment.GameRootDirectory, MelonEnvironment.GameExecutableName)}_Data"; - if (!Directory.Exists(appData)) + if (!Directory.Exists(GameDataPath)) return false; - return File.Exists(Path.Combine(appData, "globalgamemanagers")) - || File.Exists(Path.Combine(appData, "data.unity3d")) - || File.Exists(Path.Combine(appData, "mainData")); + return File.Exists(Path.Combine(GameDataPath, "globalgamemanagers")) + || File.Exists(Path.Combine(GameDataPath, "data.unity3d")) + || File.Exists(Path.Combine(GameDataPath, "mainData")); } } + internal static string GameDataPath { get; private set; } = $"{Path.Combine(MelonEnvironment.GameRootDirectory, MelonEnvironment.GameExecutableName)}_Data"; + public void Startup() { - + // Get GameAssembly Name + string gameAssemblyName = "GameAssembly"; + if (MelonUtils.IsUnix) + gameAssemblyName += ".so"; + if (MelonUtils.IsWindows) + gameAssemblyName += ".dll"; + if (MelonUtils.IsMac) + gameAssemblyName += ".dylib"; + + // Check if GameAssembly exists + string gameAssemblyPath = Path.Combine(MelonEnvironment.GameRootDirectory, gameAssemblyName); + if (File.Exists(gameAssemblyPath)) + Il2Cpp.Startup(gameAssemblyPath); // Start Il2Cpp Support + else + Mono.Startup(); // Start Mono Support } } } \ No newline at end of file diff --git a/MelonLoader/Modules/Unity/Unity.Bootstrap/Il2Cpp.cs b/MelonLoader/Modules/Unity/Unity.Bootstrap/Il2Cpp.cs new file mode 100644 index 000000000..2689ae5ad --- /dev/null +++ b/MelonLoader/Modules/Unity/Unity.Bootstrap/Il2Cpp.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MelonLoader.Unity +{ + internal static class Il2Cpp + { + internal static void Startup(string gameAssemblyPath) + { + + } + } +} diff --git a/MelonLoader/Modules/Unity/Unity.Bootstrap/Mono.cs b/MelonLoader/Modules/Unity/Unity.Bootstrap/Mono.cs new file mode 100644 index 000000000..6805863e5 --- /dev/null +++ b/MelonLoader/Modules/Unity/Unity.Bootstrap/Mono.cs @@ -0,0 +1,155 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using MelonLoader.Bootstrap; +using MelonLoader.Shared.Utils; + +namespace MelonLoader.Unity +{ + internal static class Mono + { + internal static unsafe void Startup() + { + // Scan the Game for Information about the Mono Runtime + RuntimeInfo runtimeInfo = RuntimeInfo.Get(); + + // Check if it found any Mono variant library + if ((runtimeInfo == null) + || string.IsNullOrEmpty(runtimeInfo.FilePath)) + { + Assertion.ThrowInternalFailure($"Failed to find Mono or MonoBleedingEdge Library!"); + return; + } + + // Check if there is a Posix Helper included with the Mono variant library + if (!string.IsNullOrEmpty(runtimeInfo.PosixPath)) + { + // Force-Load the Posix Helper before the actual Mono variant library + // Otherwise can cause crashing in some cases + if (!NativeLibrary.TryLoad(runtimeInfo.PosixPath, out IntPtr monoPosixHandle)) + { + Assertion.ThrowInternalFailure($"Failed to load {runtimeInfo.FolderVariant} Posix Helper from {runtimeInfo.PosixPath}!"); + return; + } + } + + // Load the Mono variant library + if (!NativeLibrary.TryLoad(runtimeInfo.FilePath, out IntPtr monoHandle)) + { + Assertion.ThrowInternalFailure($"Failed to load {runtimeInfo.FolderVariant} Library from {runtimeInfo.FilePath}!"); + return; + } + + // Get mono_jit_init_version Export + if (!NativeLibrary.TryGetExport(monoHandle, "mono_jit_init_version", out IntPtr initPtr)) + { + Assertion.ThrowInternalFailure($"Failed to get mono_jit_init_version Export from {runtimeInfo.FolderVariant} Library!"); + return; + } + + // Get mono_runtime_invoke Export + if (!NativeLibrary.TryGetExport(monoHandle, "mono_runtime_invoke", out IntPtr runtimeInvokePtr)) + { + Assertion.ThrowInternalFailure($"Failed to get mono_runtime_invoke Export from {runtimeInfo.FolderVariant} Library!"); + return; + } + + // Hook mono_jit_init_version + BootstrapInterop.HookAttach(initPtr, ((d_mono_jit_init_version)mono_jit_init_version).GetFunctionPointer()); + + // Hook mono_runtime_invoke + BootstrapInterop.HookAttach(runtimeInvokePtr, ((d_mono_runtime_invoke)mono_runtime_invoke).GetFunctionPointer()); + } + + private unsafe delegate void* d_mono_jit_init_version(void* name, void* version); + private static unsafe void* mono_jit_init_version(void* name, void* version) + { + return (void*)0; + } + + private unsafe delegate void* d_mono_runtime_invoke(void* method, void* obj, void** prams, void** exec); + private static unsafe void* mono_runtime_invoke(void* method, void* obj, void** prams, void** exec) + { + return (void*)0; + } + + private class RuntimeInfo + { + internal readonly string FilePath; + internal readonly string PosixPath; + internal readonly string FolderVariant; + internal readonly bool IsOldMono; + + private RuntimeInfo(string filePath, string posixPath, string folderVariant, bool isOldMono) + { + FilePath = filePath; + PosixPath = posixPath; + FolderVariant = folderVariant; + IsOldMono = isOldMono; + } + + internal static RuntimeInfo Get() + { + // Folders the Mono folders might be located in + string[] directoriesToSearch = new string[] + { + MelonEnvironment.GameRootDirectory, + Bootstrap.GameDataPath + }; + + // Variants of Mono folders + string[] monoFolderVariants = new string[] + { + "Mono", + "MonoBleedingEdge" + }; + + // Get Mono variant library file name + string monoFileNameWithoutExt = "mono"; + if (MelonUtils.IsUnix || MelonUtils.IsMac) + monoFileNameWithoutExt = $"lib{monoFileNameWithoutExt}"; + + // Get Mono Posix Helper file name + string monoPosixFileNameWithoutExt = "MonoPosixHelper"; + if (MelonUtils.IsUnix || MelonUtils.IsMac) + monoPosixFileNameWithoutExt = "libmonoposixhelper"; + + // Get Platform Used Extension + string monoFileExt = ".dll"; + if (MelonUtils.IsUnix) + monoFileExt = ".so"; + if (MelonUtils.IsMac) + monoFileExt = ".dylib"; + + bool isOldMono = true; + foreach (var variant in monoFolderVariants) + { + foreach (var dir in directoriesToSearch) + { + string dirPath = Path.Combine(Path.Combine(dir, variant), "EmbedRuntime"); + if (!Directory.Exists(dirPath)) + continue; + + string[] foundFiles = Directory.GetFiles(dirPath); + if ((foundFiles == null) + || (foundFiles.Length <= 0)) + continue; + + string posixPath = Path.Combine(dirPath, $"{monoPosixFileNameWithoutExt}{monoFileExt}"); + foreach (var filePath in foundFiles) + { + string fileName = Path.GetFileName(filePath); + if (fileName.Equals($"{monoFileNameWithoutExt}{monoFileExt}") + || (fileName.StartsWith($"{monoFileNameWithoutExt}-") && fileName.EndsWith(monoFileExt))) + return new RuntimeInfo(filePath, posixPath, variant, isOldMono); + } + } + + isOldMono = false; + } + + return null; + } + } + } +}