Skip to content

Commit

Permalink
Fix NetCore for games started with dotnet executable
Browse files Browse the repository at this point in the history
  • Loading branch information
bbepis committed Aug 4, 2022
1 parent 9c2b17f commit 42b8b96
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 24 deletions.
2 changes: 1 addition & 1 deletion BepInEx.NetCore/BepInEx.NetCore.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFrameworks>netcoreapp3.1;net6</TargetFrameworks>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Expand Down
108 changes: 99 additions & 9 deletions BepInEx.NetCore/HookEntrypoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ internal class StartupHook
{
public static List<string> ResolveDirectories = new();

public static string DoesNotExistPath = "_doesnotexist_.exe";

public static void Initialize()
{
var silentExceptionLog = $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log";
var silentExceptionLog = $"bepinex_preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log";

try
{
string filename, gameDirectory;

//#if DEBUG
// filename =
// Path.Combine(Directory.GetCurrentDirectory(),
Expand All @@ -28,25 +28,115 @@ public static void Initialize()
// // for debugging within VS
// ResolveDirectories.Add(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
//#else
filename = Process.GetCurrentProcess().MainModule.FileName;
gameDirectory = Path.GetDirectoryName(filename);
ResolveDirectories.Add(Path.Combine(gameDirectory, "BepInEx", "core"));
//#endif

var executableFilename = Process.GetCurrentProcess().MainModule.FileName;

var assemblyFilename = TryDetermineAssemblyNameFromDotnet(executableFilename)
?? TryDetermineAssemblyNameFromStubExecutable(executableFilename)
?? TryDetermineAssemblyNameFromCurrentAssembly(executableFilename);

string gameDirectory = null;

if (assemblyFilename != null)
gameDirectory = Path.GetDirectoryName(assemblyFilename);

string bepinexCoreDirectory = null;

if (gameDirectory != null)
bepinexCoreDirectory = Path.Combine(gameDirectory, "BepInEx", "core");

if (assemblyFilename == null || gameDirectory == null || !Directory.Exists(bepinexCoreDirectory))
{
throw new Exception("Could not determine game location, or BepInEx install location");
}

silentExceptionLog = Path.Combine(gameDirectory, silentExceptionLog);

ResolveDirectories.Add(bepinexCoreDirectory);
//#endif

AppDomain.CurrentDomain.AssemblyResolve += SharedEntrypoint.RemoteResolve(ResolveDirectories);

NetCorePreloaderRunner.OuterMain(filename);
NetCorePreloaderRunner.OuterMain(assemblyFilename);
}
catch (Exception ex)
{
File.WriteAllText(silentExceptionLog, ex.ToString());
string executableLocation = null;
string arguments = null;

try
{
executableLocation = Process.GetCurrentProcess().MainModule?.FileName;
arguments = string.Join(' ', Environment.GetCommandLineArgs());
}
catch { }

string exceptionString = $"Unhandled fatal exception\r\n" +
$"Executable location: {executableLocation ?? "<null>"}\r\n" +
$"Arguments: {arguments ?? "<null>"}\r\n" +
$"{ex}";

File.WriteAllText(silentExceptionLog, exceptionString);

Console.WriteLine("Unhandled exception");
Console.WriteLine($"Executable location: {executableLocation ?? "<null>"}");
Console.WriteLine($"Arguments: {arguments ?? "<null>"}");
Console.WriteLine(ex);
}
}

private static string TryDetermineAssemblyNameFromDotnet(string executableFilename)
{
if (Path.GetFileNameWithoutExtension(executableFilename) == "dotnet")
{
// We're in a special setup that uses dotnet directly to start a .dll, instead of a .exe that launches dotnet implicitly

var args = Environment.GetCommandLineArgs();

foreach (var arg in args)
{
if (!arg.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)
&& !arg.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
{
continue;
}

if (File.Exists(arg))
{
return Path.GetFullPath(arg);
}
}
}

return null;
}

private static string TryDetermineAssemblyNameFromStubExecutable(string executableFilename)
{
string dllFilename = Path.ChangeExtension(executableFilename, ".dll");

if (File.Exists(dllFilename))
return dllFilename;

return null;
}

private static string TryDetermineAssemblyNameFromCurrentAssembly(string executableFilename)
{
string assemblyLocation = typeof(StartupHook).Assembly.Location.Replace('/', Path.DirectorySeparatorChar);

string coreFolderPath = Path.GetDirectoryName(assemblyLocation);

if (coreFolderPath == null)
return null; // throw new Exception("Could not find a valid path to the BepInEx directory");

string gameDirectory = Path.GetDirectoryName(Path.GetDirectoryName(coreFolderPath));

if (gameDirectory == null)
return null; // throw new Exception("Could not find a valid path to the game directory");

return Path.Combine(gameDirectory, DoesNotExistPath);
}
}

namespace BepInEx.NetCore
Expand Down
35 changes: 21 additions & 14 deletions BepInEx.NetCore/NetCorePreloader.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Diagnostics;
using System.IO;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using BepInEx.NetLauncher.Common;
Expand All @@ -20,38 +19,46 @@ public static void Start()
var preloaderListener = new PreloaderConsoleListener();
Logger.Listeners.Add(preloaderListener);

var entrypointAssemblyPath = Path.ChangeExtension(Process.GetCurrentProcess().MainModule.FileName, "dll");
string entrypointAssemblyPath = !Paths.ExecutablePath.EndsWith(StartupHook.DoesNotExistPath) ? Paths.ExecutablePath : null;

Paths.SetExecutablePath(entrypointAssemblyPath);

TypeLoader.SearchDirectories.Add(Path.GetDirectoryName(entrypointAssemblyPath));
TypeLoader.SearchDirectories.Add(Paths.GameRootPath);

Logger.Sources.Add(TraceLogSource.CreateSource());

ChainloaderLogHelper.PrintLogInfo(Log);

Log.Log(LogLevel.Info, $"CLR runtime version: {Environment.Version}");
Log.LogInfo($"CLR runtime version: {Environment.Version}");

Log.LogInfo($"Current executable: {Process.GetCurrentProcess().MainModule.FileName}");
Log.LogInfo($"Entrypoint assembly: {entrypointAssemblyPath ?? "<does not exist>"}");
Log.LogInfo($"Launch arguments: {string.Join(' ', Environment.GetCommandLineArgs())}");


AssemblyBuildInfo executableInfo;

using (var entrypointAssembly = AssemblyDefinition.ReadAssembly(entrypointAssemblyPath))
executableInfo = AssemblyBuildInfo.DetermineInfo(entrypointAssembly);

Log.LogInfo($"Game executable build architecture: {executableInfo}");
if (entrypointAssemblyPath != null)
{
using (var entrypointAssembly = AssemblyDefinition.ReadAssembly(entrypointAssemblyPath))
executableInfo = AssemblyBuildInfo.DetermineInfo(entrypointAssembly);

Log.LogInfo($"Game executable build architecture: {executableInfo}");
}
else
{
Log.LogWarning("Game assembly is unknown, can't determine build architecture");
}

Log.Log(LogLevel.Message, "Preloader started");
Log.LogMessage("Preloader started");

using (var assemblyPatcher = new AssemblyPatcher())
{
assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);

Log.Log(LogLevel.Info,
$"{assemblyPatcher.PatcherContext.PatchDefinitions.Count} patcher definition(s) loaded");
Log.LogInfo($"{assemblyPatcher.PatcherContext.PatchDefinitions.Count} patcher definition(s) loaded");

assemblyPatcher.LoadAssemblyDirectories(new[] { Paths.GameRootPath }, new[] { "dll", "exe" });

Log.Log(LogLevel.Info, $"{assemblyPatcher.PatcherContext.AvailableAssemblies.Count} assemblies discovered");
Log.LogInfo($"{assemblyPatcher.PatcherContext.AvailableAssemblies.Count} assemblies discovered");

assemblyPatcher.PatchAndLoad();
}
Expand Down

0 comments on commit 42b8b96

Please sign in to comment.