Skip to content

Commit cd4799a

Browse files
Fixed Initialization Issues with native Mono and Il2Cpp games on Linux
1 parent f3eedb4 commit cd4799a

File tree

8 files changed

+160
-190
lines changed

8 files changed

+160
-190
lines changed

MelonLoader.Bootstrap/MelonLoader.Bootstrap.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@
6464

6565
<ItemGroup>
6666
<Compile Include="$(SolutionDir)/MelonLoader/LoaderConfig.cs" />
67-
<Compile Include="..\MelonLoader\NativeUtils\NativeHooks.cs" Link="NativeHooks.cs" />
6867
</ItemGroup>
6968

7069
<ItemGroup Condition="'$(RuntimeIdentifier.StartsWith(`osx-`))' == 'true'">

MelonLoader.Bootstrap/ModuleSymbolRedirect.cs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ internal static void Attach()
3030
("dlsym", detourPtr)
3131
]);
3232
#endif
33+
3334
#if WINDOWS
3435
PltHook.InstallHooks
3536
([
@@ -40,59 +41,58 @@ internal static void Attach()
4041
MelonDebug.Log("Symbol Redirect Attached!");
4142
}
4243

43-
internal static nint GetSymbol(nint handle, string symbolName)
44-
=> GetSymbol(handle, Marshal.StringToHGlobalAnsi(symbolName));
45-
internal static nint GetSymbol(nint handle, nint symbolName)
44+
internal static nint GetSymbol(nint handle, string? symbol)
4645
{
4746
if ((handle == nint.Zero)
48-
|| (symbolName == nint.Zero))
47+
|| string.IsNullOrEmpty(symbol))
4948
return nint.Zero;
5049

50+
// Herp: Using Prebuilt Interop classes for this caused weird crashing issues when attempting to marshal string to span
51+
// This works around the issue by manually importing the appropriate original export into a delegate and then calling original using that instead
5152
#if WINDOWS
52-
return GetProcAddress(handle, symbolName);
53+
return WindowsNative.GetProcAddress(handle, symbol);
5354
#elif LINUX || OSX
54-
return dlsym(handle, symbolName);
55+
return LibcNative.Dlsym(handle, symbol);
5556
#else
5657
return nint.Zero;
5758
#endif
5859
}
60+
internal static nint GetSymbol(nint handle, nint symbol)
61+
{
62+
if ((handle == nint.Zero)
63+
|| (symbol == nint.Zero))
64+
return nint.Zero;
65+
66+
return GetSymbol(handle, Marshal.PtrToStringAnsi(symbol));
67+
}
5968

6069
private static nint SymbolDetour(nint handle, nint symbol)
6170
{
62-
nint originalSymbolAddress = GetSymbol(handle, symbol);
63-
6471
string? symbolName = Marshal.PtrToStringAnsi(symbol);
6572
if (string.IsNullOrEmpty(symbolName)
6673
|| string.IsNullOrWhiteSpace(symbolName))
67-
return originalSymbolAddress;
74+
return nint.Zero;
75+
76+
nint originalSymbolAddress = GetSymbol(handle, symbolName);
77+
if (originalSymbolAddress == nint.Zero)
78+
return nint.Zero;
6879

69-
//MelonDebug.Log($"Looking for Symbol {symbolName}");
80+
MelonDebug.Log($"Looking for Symbol {symbolName}");
7081
if (!MonoHandler.SymbolRedirects.TryGetValue(symbolName, out var redirect)
7182
&& !Il2CppHandler.SymbolRedirects.TryGetValue(symbolName, out redirect))
7283
return originalSymbolAddress;
7384

7485
if (!_runtimeInitialised)
7586
{
76-
_runtimeInitialised = true;
77-
MelonDebug.Log("Initializing Runtime");
87+
MelonDebug.Log("Init");
7888
redirect.InitMethod(handle);
7989
if (!LoaderConfig.Current.Loader.CapturePlayerLogs)
8090
ConsoleHandler.ResetHandles();
8191
}
92+
_runtimeInitialised = true;
8293

8394
MelonDebug.Log($"Redirecting {symbolName}");
8495
return redirect.detourPtr;
8596
}
86-
87-
#if WINDOWS
88-
[DllImport("kernel32")]
89-
private static extern nint GetProcAddress(nint handle, nint symbol);
90-
#elif LINUX
91-
[DllImport("libdl.so.2")]
92-
private static extern IntPtr dlsym(nint handle, nint symbol);
93-
#elif OSX
94-
[DllImport("libSystem.B.dylib")]
95-
private static extern IntPtr dlsym(nint handle, nint symbol);
96-
#endif
9797
}
98-
}
98+
}

MelonLoader.Bootstrap/RuntimeHandlers/Il2Cpp/Dotnet.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public static bool InitializeForRuntimeConfig(string runtimeConfigPath, out nint
8181

8282
if (status != 0)
8383
{
84-
context = 0;
84+
context = status;
8585
return false;
8686
}
8787

MelonLoader.Bootstrap/RuntimeHandlers/Il2Cpp/Il2CppHandler.cs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,9 @@ private static void InitializeManaged()
111111

112112
internal static nint InvokeDetour(nint method, nint obj, nint args, nint exc)
113113
{
114-
if (invokeStarted)
115-
return il2cpp.RuntimeInvoke(method, obj, args, exc);
116-
117114
var result = il2cpp.RuntimeInvoke(method, obj, args, exc);
115+
if (invokeStarted)
116+
return result;
118117

119118
var name = il2cpp.GetMethodName(method);
120119
if (name == null || !name.Contains("Internal_ActiveSceneChanged"))
@@ -133,16 +132,6 @@ private static void Start()
133132
startFunc?.Invoke();
134133
}
135134

136-
private static unsafe void NativeHookAttachImpl(nint* target, nint detour)
137-
{
138-
*target = Dobby.HookAttach(*target, detour);
139-
}
140-
141-
private static unsafe void NativeHookDetachImpl(nint* target, nint detour)
142-
{
143-
Dobby.HookDetach(*target);
144-
}
145-
146135
// Requires the bootstrap handle to be passed first
147136
private delegate void InitializeFn(ref nint startFunc);
148-
}
137+
}

MelonLoader/Core.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,12 @@ internal static int Initialize()
145145
Fixes.Il2CppInterop.Il2CppInteropExceptionLog.Install();
146146

147147
#if OSX
148-
Fixes.Il2CppInterop.Il2CppInteropMacFix.Install();
149148
Fixes.Dotnet.NativeLibraryFix.Install();
150149
#endif
151150

152151
Fixes.Il2CppInterop.Il2CppInteropFixes.Install();
152+
Fixes.Il2CppInterop.Il2CppInteropInjectorHelpersSetupFix.Install();
153153
Fixes.Il2CppInterop.Il2CppInteropGetFieldDefaultValueFix.Install();
154-
Fixes.Il2CppInterop.Il2CppInteropGenericMethodGetMethodFix.Install();
155-
156154
Fixes.Il2CppInterop.Il2CppICallInjector.Install();
157155

158156
#endif
@@ -211,8 +209,8 @@ internal static bool Start()
211209
if (!SupportModule.Setup())
212210
return false;
213211

214-
//MelonDebug.Msg("Invoking AddUnityDebugLog");
215-
//AddUnityDebugLog();
212+
MelonDebug.Msg("Invoking AddUnityDebugLog");
213+
AddUnityDebugLog();
216214

217215
#if NET6_0_OR_GREATER
218216
RegisterTypeInIl2Cpp.SetReady();

MelonLoader/Fixes/Il2CppInterop/Il2CppInteropGenericMethodGetMethodFix.cs

Lines changed: 0 additions & 95 deletions
This file was deleted.
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#if NET6_0_OR_GREATER
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Reflection;
6+
using System.Reflection.Emit;
7+
using HarmonyLib;
8+
using Il2CppInterop.Runtime.Injection;
9+
using MelonLoader.InternalUtils;
10+
11+
namespace MelonLoader.Fixes.Il2CppInterop
12+
{
13+
// Herp: This fixes an XRef issue with Il2CppInterop's GenericMethod::GetMethod Hook on some Unity Builds
14+
// Unity 2020.3.x
15+
// Unity 6000.x.x+
16+
internal static class Il2CppInteropInjectorHelpersSetupFix
17+
{
18+
private static Type _injectorType;
19+
private static MethodInfo _setupMethod;
20+
private static MethodInfo _setupPatch;
21+
22+
internal static void Install()
23+
{
24+
try
25+
{
26+
Type thisType = typeof(Il2CppInteropInjectorHelpersSetupFix);
27+
28+
_injectorType = typeof(ClassInjector).Assembly.GetType("Il2CppInterop.Runtime.Injection.InjectorHelpers", false);
29+
if (_injectorType == null)
30+
throw new Exception($"Failed to get InjectorHelpers");
31+
32+
_setupMethod = _injectorType.GetMethod("Setup", BindingFlags.NonPublic | BindingFlags.Static);
33+
if (_setupMethod == null)
34+
throw new Exception("Failed to get InjectorHelpers.Setup");
35+
36+
_setupPatch = thisType.GetMethod(nameof(Setup_Transpiler), BindingFlags.NonPublic | BindingFlags.Static);
37+
MelonDebug.Msg($"Patching Il2CppInterop InjectorHelpers.Setup...");
38+
Core.HarmonyInstance.Patch(_setupMethod,
39+
null,
40+
null,
41+
new HarmonyMethod(_setupPatch));
42+
}
43+
catch (Exception e)
44+
{
45+
MelonLogger.Error(e);
46+
}
47+
}
48+
49+
private static bool ShouldUseNewHook()
50+
{
51+
if (UnityInformationHandler.EngineVersion.Major >= 6000)
52+
return true;
53+
54+
if ((UnityInformationHandler.EngineVersion.Major == 2020)
55+
&& (UnityInformationHandler.EngineVersion.Minor == 3)
56+
&& (UnityInformationHandler.EngineVersion.Build >= 48))
57+
return true;
58+
59+
if ((UnityInformationHandler.EngineVersion.Major == 2022)
60+
&& (UnityInformationHandler.EngineVersion.Minor == 3)
61+
&& (UnityInformationHandler.EngineVersion.Build >= 62))
62+
return true;
63+
64+
return false;
65+
}
66+
67+
private static IEnumerable<CodeInstruction> Setup_Transpiler(IEnumerable<CodeInstruction> instructions)
68+
{
69+
#if OSX
70+
// This hook isn't reliable on macOS due to potential inlining by the player's compiler resulting in il2cppinterop
71+
// thinking it found the right function, but it actually found one that takes a pointer which it would then incorrectly hook
72+
// resulting in a crash. While it can hinder class injection functionality, it isn't needed for the game to boot,
73+
// and it didn't prevent UnityExplorer to function
74+
instructions = RemoveHookCall("GetTypeInfoFromTypeDefinitionIndexHook", instructions);
75+
#endif
76+
77+
if (!ShouldUseNewHook())
78+
return instructions;
79+
80+
instructions = ReplaceGenericMethodGetMethodHook(instructions);
81+
82+
#if LINUX
83+
instructions = RemoveHookCall("GetFieldDefaultValueHook", instructions);
84+
#endif
85+
86+
return instructions;
87+
}
88+
89+
private static IEnumerable<CodeInstruction> ReplaceGenericMethodGetMethodHook(IEnumerable<CodeInstruction> instructions)
90+
{
91+
var codeMatcher = new CodeMatcher(instructions);
92+
codeMatcher.MatchStartForward([
93+
new(i => i.LoadsField(AccessTools.Field(_injectorType, "GenericMethodGetMethodHook")))
94+
]).Operand = AccessTools.Field(_injectorType, "GenericMethodGetMethodHook_Unity6");
95+
return codeMatcher.InstructionEnumeration();
96+
}
97+
98+
private static IEnumerable<CodeInstruction> RemoveHookCall(string hookFieldName, IEnumerable<CodeInstruction> instructions)
99+
{
100+
var field = AccessTools.Field(_injectorType, hookFieldName);
101+
102+
var matcher = new CodeMatcher(instructions);
103+
104+
matcher.MatchStartForward(
105+
new CodeMatch(i => i.LoadsField(field))
106+
);
107+
108+
if (!matcher.IsValid)
109+
return instructions;
110+
111+
// Capture labels from the first instruction being removed
112+
var labels = matcher.Instruction.labels;
113+
114+
// Move to next instruction AFTER the removed block
115+
matcher.Advance(2);
116+
117+
// Reattach labels so branches still work
118+
matcher.Instruction.labels.AddRange(labels);
119+
120+
// Go back and remove the original instructions
121+
matcher.Advance(-2);
122+
matcher.RemoveInstructions(2);
123+
124+
return matcher.InstructionEnumeration();
125+
}
126+
}
127+
}
128+
129+
#endif

0 commit comments

Comments
 (0)