v5: Fix crash on macOS arm64: wrap XTermFix and ConsoleSetOutFix in TryDo (#1288)

On macOS arm64 (Apple Silicon), MonoMod's DetourHelper.GetIdentifiable()
returns null for certain methods (e.g. Console.SetOut, TermInfoReader
methods). The ILHook constructor does not guard against this null return,
causing a NullReferenceException that propagates out of XTermFix.Apply()
and ConsoleSetOutFix.Apply() uncaught, which terminates BepInEx
initialization before Preloader.Run() is ever called.

These two fixes are intentionally called before ConsoleManager.Initialize
in PreloaderMain() so they cannot simply be moved into Run(). Instead,
wrap each with Utility.TryDo() (the same pattern already used for
UnityPatches.Apply in Run()) and store any exception for deferred logging
once the BepInEx logger is available.

Result: on macOS arm64, BepInEx now fully initializes — Preloader and
Chainloader complete, all plugins load — with a LogWarning noting which
patches could not be applied rather than a silent hard crash.
This commit is contained in:
theoriginalbrian authored and GitHub committed 2026-06-11 11:50:37 +02:00
1 parent c69012ad1f
commit b10beef59b
2 files changed
+20 -2

No files matched your search

+14 -2
View File
@@ -10,6 +10,12 @@ namespace BepInEx.Preloader
{
internal static class PreloaderRunner
{
/// <summary>
/// Exceptions thrown by XTermFix.Apply() and ConsoleSetOutFix.Apply() during early preloader init,
/// stored for deferred logging in <see cref="Preloader.Run"/> once the logger is available.
/// </summary>
internal static Exception XTermFixException;
internal static Exception ConsoleSetOutFixException;
// This is a list of important assemblies in BepInEx core folder that should be force-loaded
// Some games can ship these assemblies in Managed folder, in which case assembly resolving bypasses our LocalResolve
// On the other hand, renaming these assemblies is not viable because 3rd party assemblies
@@ -62,8 +68,14 @@ namespace BepInEx.Preloader
{
if (Preloader.ConfigApplyRuntimePatches.Value)
{
XTermFix.Apply();
ConsoleSetOutFix.Apply();
// These fixes must run before ConsoleManager.Initialize (which opens the terminal),
// so they cannot be placed inside Preloader.Run(). Wrap them individually so that
// a failure on one platform (e.g. macOS arm64, where MonoMod's ILHook throws a
// NullReferenceException in DetourHelper.GetIdentifiable) does not prevent BepInEx
// from initializing at all. Exceptions are stored for deferred logging once the
// BepInEx logger is available in Preloader.Run().
Utility.TryDo(() => XTermFix.Apply(), out XTermFixException);
Utility.TryDo(() => ConsoleSetOutFix.Apply(), out ConsoleSetOutFixException);
}
Preloader.Run();
+6
View File
@@ -73,6 +73,12 @@ namespace BepInEx.Preloader
Logger.LogInfo($"Supports SRE: {Utility.CLRSupportsDynamicAssemblies}");
Logger.LogInfo($"System platform: {PlatformHelper.Current}");
if (PreloaderRunner.XTermFixException != null)
Logger.LogWarning($"Failed to apply XTermFix runtime patch. See more info in the output log. Error message: {PreloaderRunner.XTermFixException.Message}");
if (PreloaderRunner.ConsoleSetOutFixException != null)
Logger.LogWarning($"Failed to apply ConsoleSetOutFix runtime patch. See more info in the output log. Error message: {PreloaderRunner.ConsoleSetOutFixException.Message}");
if (runtimePatchException != null)
Logger.LogWarning($"Failed to apply runtime patches for Mono. See more info in the output log. Error message: {runtimePatchException.Message}");