fix: work around Consolonia libcoreclr.so DllNotFoundException on Linux single-file publish

Consolonia's Unix.Terminal uses [DllImport("libcoreclr.so")] to load
dlopen/dlsym on .NET Core. It ships a SetDllImportResolver that maps
the library name to the current process handle, but the resolver is
guarded by #if NET6_0 (exact TFM match) instead of NET6_0_OR_GREATER.
Since Consolonia targets net8.0, the resolver is never compiled in.

On self-contained single-file publishes, libcoreclr.so is bundled
inside the host binary and does not exist on disk. Without the
resolver the OS linker cannot find it, causing a DllNotFoundException
that crashes the TUI on startup. This primarily affects ARM64 Linux
users (e.g. Raspberry Pi / Debian Trixie) who almost exclusively use
self-contained publishes.

This commit registers an AssemblyLoadContext.Default.ResolvingUnmanagedDll
handler in Program.Main (before TUI init) that returns (IntPtr)(-1) for
libcoreclr.so, which the runtime interprets as the current process.

This is a temporary workaround until the upstream fix lands:
https://github.com/Consolonia/Consolonia/pull/605

Made-with: Cursor
This commit is contained in:
BruceChen 2026-04-03 01:10:06 +08:00
parent 82ebdd00ee
commit 115a6f0fcc

View file

@ -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);
}
/// <summary>
/// Consolonia's Unix.Terminal uses <c>[DllImport("libcoreclr.so")]</c> to reach
/// <c>dlopen</c>/<c>dlsym</c> on .NET Core. The library ships a
/// <c>SetDllImportResolver</c> that maps <c>libcoreclr.so</c> to the current
/// process, but it is compiled under <c>#if NET6_0</c> (exact TFM match) instead
/// of <c>NET6_0_OR_GREATER</c>, so it is dead code when the consuming project
/// targets net8.0+. On a self-contained single-file publish the physical
/// <c>libcoreclr.so</c> does not exist on the search path, causing a
/// <c>DllNotFoundException</c> that crashes the TUI.
///
/// We work around this by registering our own resolver before any Consolonia
/// code runs: if any assembly asks for <c>libcoreclr.so</c> we return
/// <c>(IntPtr)(-1)</c> which the runtime interprets as "the current process".
/// </summary>
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;