Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion BepInEx.Core/BepInEx.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All"/>
<PackageReference Include="SemanticVersioning" Version="3.0.0"/>
<PackageReference Include="MonoMod.Utils" Version="25.0.13"/>
<PackageReference Include="MonoMod.Utils" Version="25.0.14"/>
<PackageReference Include="Mono.Cecil" Version="0.11.6" />
<PackageReference Include="System.Drawing.Common" Version="10.0.2" />
</ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All"/>
<PackageReference Include="MonoMod.Utils" Version="25.0.13"/>
<PackageReference Include="MonoMod.Utils" Version="25.0.14"/>
</ItemGroup>
</Project>
106 changes: 71 additions & 35 deletions Runtimes/NET/BepisLoader/BepisLoader.cs
Original file line number Diff line number Diff line change
@@ -1,40 +1,54 @@
using System.Reflection;
using System.Runtime.Loader;
using System.Runtime.InteropServices;

namespace BepisLoader;

public class BepisLoader

Check warning on line 7 in Runtimes/NET/BepisLoader/BepisLoader.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'BepisLoader'

Check warning on line 7 in Runtimes/NET/BepisLoader/BepisLoader.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'BepisLoader'
{
internal static string resoDir = string.Empty;
internal static AssemblyLoadContext alc = null!;
internal static AssemblyDependencyResolver? resolver;
static void Main(string[] args)
{
resoDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
#if DEBUG
Comment thread
hazre marked this conversation as resolved.
logPath = Path.Combine(resoDir, "BepisLoader.log");
File.WriteAllText(logPath, "BepisLoader started\n");
#endif

alc = new BepisLoadContext();

// The game runs in the Default AssemblyLoadContext, not our custom BepisLoadContext. When code in the Default ALC requests a dependency, BepisLoadContext.Load() is never called, only this global AssemblyResolve event fires as a fallback.
AppDomain.CurrentDomain.AssemblyResolve += ResolveGameDll;
resoDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? AppContext.BaseDirectory;

var bepinPath = Path.Combine(resoDir, "BepInEx");
var bepinArg = Array.IndexOf(args.Select(x => x?.ToLowerInvariant()).ToArray(), "--bepinex-target");
if (bepinArg != -1 && args.Length > bepinArg + 1)
{
bepinPath = args[bepinArg + 1];
}
Log("Loading BepInEx from " + bepinPath);

var asm = alc.LoadFromAssemblyPath(Path.Combine(bepinPath, "core", "BepInEx.NET.CoreCLR.dll"));
logPath = Path.Combine(bepinPath, "EntryPoint.log");
try
{
Directory.CreateDirectory(bepinPath);
File.WriteAllText(logPath, string.Empty);
}
catch
{
}
Log("BepisLoader started");

// The Default ALC only probes BepisLoader.deps.json (no native entries), so resolve its native dependencies (e.g. System.Net.Quic loading libmsquic) via the game's deps.json.
var resoDllPath = GetResoDllPath();
if (File.Exists(resoDllPath))
resolver = new AssemblyDependencyResolver(resoDllPath);
AssemblyLoadContext.Default.ResolvingUnmanagedDll += ResolveDefaultUnmanagedDll;

alc = new BepisLoadContext();

// The game runs in the Default AssemblyLoadContext, not our custom BepisLoadContext. When code in the Default ALC requests a dependency, BepisLoadContext.Load() is never called, only this global AssemblyResolve event fires as a fallback.
AppDomain.CurrentDomain.AssemblyResolve += ResolveGameDll;

Log("Loading BepInEx from " + bepinPath);

var asm = alc.LoadFromAssemblyPath(Path.Combine(bepinPath, "core", "BepInEx.NET.CoreCLR.dll"));

var t = asm.GetType("StartupHook");
var m = t.GetMethod("Initialize", BindingFlags.Public | BindingFlags.Static, [typeof(string), typeof(string), typeof(AssemblyLoadContext)]);

Check warning on line 50 in Runtimes/NET/BepisLoader/BepisLoader.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 50 in Runtimes/NET/BepisLoader/BepisLoader.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
m.Invoke(null, [resoDllPath, bepinPath, alc]);

Check warning on line 51 in Runtimes/NET/BepisLoader/BepisLoader.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 51 in Runtimes/NET/BepisLoader/BepisLoader.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

// Find and load Resonite
var resoAsm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.GetName().Name == "Renderite.Host");
Expand All @@ -50,7 +64,8 @@
}
catch (Exception e)
{
File.WriteAllLines(Path.Combine(resoDir, "BepisCrash.log"), [DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " - Resonite crashed", e.ToString()]);
Log("Resonite crashed: " + e);
File.WriteAllLines(Path.Combine(bepinPath, "BepisCrash.log"), [DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " - Resonite crashed", e.ToString()]);
}
}

Expand Down Expand Up @@ -85,16 +100,41 @@
return File.Exists(path) ? path : Path.Combine(resoDir, "Resonite.dll");
}

static IntPtr ResolveNativeLibrary(string libraryName, AssemblyDependencyResolver? resolver, Func<string, IntPtr> load)
{
Log($"Loading native library: {libraryName}");
string? libraryPath = resolver?.ResolveUnmanagedDllToPath(libraryName);
if (libraryPath == null)
{
Log($"Native library unresolved by deps.json, falling back to default load: {libraryName}");
return IntPtr.Zero;
}
try
{
var handle = load(libraryPath);
Log(handle != IntPtr.Zero
? $"Native library loaded: {libraryName} -> {libraryPath} (0x{handle:X})"
: $"Native library load failed: {libraryName} -> {libraryPath}");
return handle;
}
catch (Exception ex)
{
Log($"Native library load threw: {libraryName} -> {libraryPath} ({ex.Message})");
throw;
}
}

static IntPtr ResolveDefaultUnmanagedDll(Assembly _, string libraryName)
=> ResolveNativeLibrary(libraryName, resolver, NativeLibrary.Load);

private class BepisLoadContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver? _resolver;

public BepisLoadContext() : base(isCollectible: false)
{
var resoDllPath = GetResoDllPath();

if (File.Exists(resoDllPath))
_resolver = new AssemblyDependencyResolver(resoDllPath);
Log($"Entry assembly: {GetResoDllPath()} (RID: {RuntimeInformation.RuntimeIdentifier})");
_resolver = resolver;
}

protected override Assembly? Load(AssemblyName assemblyName)
Expand All @@ -110,29 +150,25 @@
}

protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
Log("NativeLib " + unmanagedDllName);
string? libraryPath = _resolver?.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
Log(" Resolved: " + libraryPath);
return LoadUnmanagedDllFromPath(libraryPath);
}
return IntPtr.Zero;
}
=> ResolveNativeLibrary(unmanagedDllName, _resolver, LoadUnmanagedDllFromPath);
}

#if DEBUG
private static string logPath;
private static object _lock = new object();
#endif
private static string logPath = string.Empty;
private static readonly object _lock = new object();
private static readonly HashSet<string> _loggedMessages = new(StringComparer.OrdinalIgnoreCase);
public static void Log(string message)

Check warning on line 159 in Runtimes/NET/BepisLoader/BepisLoader.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'BepisLoader.Log(string)'

Check warning on line 159 in Runtimes/NET/BepisLoader/BepisLoader.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'BepisLoader.Log(string)'
{
#if DEBUG
lock (_lock)
try
{
lock (_lock)
{
if (!_loggedMessages.Add(message))
return;
File.AppendAllLines(logPath, [$"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff zzz} [BepisLoader] {message}"]);
}
}
catch
{
File.AppendAllLines(logPath, [message]);
}
#endif
}
}
Loading