Skip to content

Commit

Permalink
More Work on Unity Support Module
Browse files Browse the repository at this point in the history
  • Loading branch information
HerpDerpinstine committed Nov 20, 2023
1 parent 81043aa commit f900cce
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 41 deletions.
11 changes: 9 additions & 2 deletions MelonLoader/MelonLoader.Bootstrap/BootstrapInterop.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
using System;

namespace MelonLoader.Bootstrap
{
public static unsafe class BootstrapInterop
{
public static delegate* unmanaged<void*, void*, void*> HookAttach;
public static delegate* unmanaged<void*, void> HookDetach;
public static delegate* unmanaged<void*, void*, void*> NativeHookAttach;
public static delegate* unmanaged<void*, void> NativeHookDetach;

public static void HookAttach(IntPtr target, IntPtr detour)
=> NativeHookAttach(target.ToPointer(), detour.ToPointer());
public static void HookDetach(IntPtr target)
=> NativeHookDetach(target.ToPointer());
}
}
8 changes: 4 additions & 4 deletions MelonLoader/MelonLoader.NativeHost/MelonLoaderInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
1 change: 0 additions & 1 deletion MelonLoader/MelonLoader.NativeHost/NativeEntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ static unsafe void LoadStage1(HostImports* imports)
imports->LoadAssemblyAndGetPtr = &Stereo.LoadAssemblyAndGetFuncPtr;
}


[UnmanagedCallersOnly]
static unsafe void LoadStage2(HostImports* imports, HostExports* exports)
{
Expand Down
51 changes: 26 additions & 25 deletions MelonLoader/MelonLoader.NativeHost/Stereo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<AssemblyName, Assembly> resolver = name => AssemblyLoadContext.Default.LoadFromAssemblyName(name);

var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
var type = Type.GetType(typeName, resolver, null, true);

Func<AssemblyName, Assembly> 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();
}
}
}
4 changes: 1 addition & 3 deletions MelonLoader/MelonLoader.Shared/Core.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.IO;
using System.Reflection;
using MelonLoader.Shared.Utils;
using MelonLoader.Shared.Utils;

namespace MelonLoader.Shared
{
Expand Down
33 changes: 33 additions & 0 deletions MelonLoader/MelonLoader.Shared/Utils/MelonExtensions.cs
Original file line number Diff line number Diff line change
@@ -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<T>(this IntPtr ptr, out T output) where T : Delegate
=> output = GetDelegate<T>(ptr);
public static T GetDelegate<T>(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
}
}
27 changes: 21 additions & 6 deletions MelonLoader/Modules/Unity/Unity.Bootstrap/Bootstrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
16 changes: 16 additions & 0 deletions MelonLoader/Modules/Unity/Unity.Bootstrap/Il2Cpp.cs
Original file line number Diff line number Diff line change
@@ -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)
{

}
}
}
155 changes: 155 additions & 0 deletions MelonLoader/Modules/Unity/Unity.Bootstrap/Mono.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}

0 comments on commit f900cce

Please sign in to comment.