diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 9e71ad8e..6f505644 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -4,6 +4,8 @@ using System.Globalization; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -174,6 +176,9 @@ namespace MinecraftClient }; // --- Determine console mode and initialize backend --- + if (!OperatingSystem.IsWindows()) + InstallCursesNativeResolver(); + if (!ConsoleIO.BasicIO && Config.Console.General.ConsoleMode == ConsoleModeType.tui) { ConsoleIO.Backend?.Shutdown(); @@ -206,6 +211,26 @@ namespace MinecraftClient RunStartupSequence(args); } + /// + /// Consolonia's Unix.Terminal uses [DllImport("libcoreclr.so")] to reach + /// dlopen/dlsym on .NET Core. The library ships a + /// SetDllImportResolver that maps libcoreclr.so to the current + /// process, but it is compiled under #if NET6_0 (exact TFM match) instead + /// of NET6_0_OR_GREATER, so it is dead code when the consuming project + /// targets net8.0+. On a self-contained single-file publish the physical + /// libcoreclr.so does not exist on the search path, causing a + /// DllNotFoundException that crashes the TUI. + /// + /// We work around this by registering our own resolver before any Consolonia + /// code runs: if any assembly asks for libcoreclr.so we return + /// (IntPtr)(-1) which the runtime interprets as "the current process". + /// + private static void InstallCursesNativeResolver() + { + AssemblyLoadContext.Default.ResolvingUnmanagedDll += (assembly, libraryName) => + libraryName == "libcoreclr.so" ? (IntPtr)(-1) : IntPtr.Zero; + } + private static void HandleTuiStartupFailure(Exception exception) { Config.Console.General.ConsoleMode = ConsoleModeType.classic;