Move .NET framework common entrypoint code to shared project, and add silent exception logging

This commit is contained in:
Bepis committed 2022-01-26 00:32:43 +11:00
1 parent 5686f6a003
commit bc20530738
8 files changed
+230 -283

No files matched your search

+4
View File
@@ -17,4 +17,8 @@
<ProjectReference Include="..\BepInEx.Preloader.Core\BepInEx.Preloader.Core.csproj" />
</ItemGroup>
<Import Project="..\BepInEx.NetLauncher.Shared\BepInEx.NetLauncher.Shared.projitems" Label="Shared" />
<Import Project="..\BepInEx.Shared\BepInEx.Shared.projitems" Label="Shared" />
</Project>
+17 -133
View File
@@ -2,10 +2,9 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using BepInEx.Logging;
using BepInEx.NetCore;
using BepInEx.NetLauncher.Shared;
using BepInEx.Preloader.Core;
internal class StartupHook
@@ -14,9 +13,11 @@ internal class StartupHook
public static void Initialize()
{
var silentExceptionLog = $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log";
try
{
string filename;
string filename, gameDirectory;
//#if DEBUG
// filename =
@@ -28,63 +29,24 @@ internal class StartupHook
// ResolveDirectories.Add(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
//#else
filename = Process.GetCurrentProcess().MainModule.FileName;
ResolveDirectories.Add(Path.Combine(Path.GetDirectoryName(filename), "BepInEx", "core"));
gameDirectory = Path.GetDirectoryName(filename);
ResolveDirectories.Add(Path.Combine(gameDirectory, "BepInEx", "core"));
//#endif
silentExceptionLog = Path.Combine(gameDirectory, silentExceptionLog);
AppDomain.CurrentDomain.AssemblyResolve += RemoteResolve;
AppDomain.CurrentDomain.AssemblyResolve += SharedEntrypoint.RemoteResolve(ResolveDirectories);
NetCorePreloaderRunner.OuterMain(filename);
}
catch (Exception ex)
{
File.WriteAllText(silentExceptionLog, ex.ToString());
Console.WriteLine("Unhandled exception");
Console.WriteLine(ex);
}
}
private static Assembly RemoteResolve(object sender, ResolveEventArgs reference)
{
var assemblyName = new AssemblyName(reference.Name);
foreach (var directory in ResolveDirectories)
{
if (!Directory.Exists(directory))
continue;
var potentialDirectories = new List<string> { directory };
potentialDirectories.AddRange(Directory.GetDirectories(directory, "*", SearchOption.AllDirectories));
var potentialFiles = potentialDirectories.Select(x => Path.Combine(x, $"{assemblyName.Name}.dll"))
.Concat(potentialDirectories.Select(x =>
Path
.Combine(x,
$"{assemblyName.Name}.exe")));
foreach (var path in potentialFiles)
{
if (!File.Exists(path))
continue;
Assembly assembly;
try
{
assembly = Assembly.LoadFrom(path);
}
catch (Exception ex)
{
continue;
}
if (assembly.GetName().Name == assemblyName.Name)
return assembly;
}
}
return null;
}
}
namespace BepInEx.NetCore
@@ -93,14 +55,14 @@ namespace BepInEx.NetCore
{
internal static void PreloaderMain()
{
ConsoleManager.Initialize(false, false);
ConsoleManager.CreateConsole();
Logger.Listeners.Add(new ConsoleLogListener());
try
{
ConsoleManager.Initialize(false, false);
ConsoleManager.CreateConsole();
Logger.Listeners.Add(new ConsoleLogListener());
NetCorePreloader.Start();
}
catch (Exception ex)
@@ -116,87 +78,9 @@ namespace BepInEx.NetCore
Paths.SetExecutablePath(filename);
AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
AppDomain.CurrentDomain.AssemblyResolve += SharedEntrypoint.LocalResolve;
PreloaderMain();
}
private static Assembly LocalResolve(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
var foundAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(x => x.GetName().Name == assemblyName.Name);
if (foundAssembly != null)
return foundAssembly;
if (LocalUtility.TryResolveDllAssembly(assemblyName, Paths.BepInExAssemblyDirectory, out foundAssembly)
|| LocalUtility.TryResolveDllAssembly(assemblyName, Paths.PatcherPluginPath, out foundAssembly)
|| LocalUtility.TryResolveDllAssembly(assemblyName, Paths.PluginPath, out foundAssembly))
return foundAssembly;
return null;
}
}
/// <summary>
/// Generic helper properties and methods.
/// </summary>
public static class LocalUtility
{
/// <summary>
/// Try to resolve and load the given assembly DLL.
/// </summary>
/// <param name="assemblyName">Name of the assembly, of the type <see cref="AssemblyName" />.</param>
/// <param name="directory">Directory to search the assembly from.</param>
/// <param name="assembly">The loaded assembly.</param>
/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
private static bool TryResolveDllAssembly<T>(AssemblyName assemblyName,
string directory,
Func<string, T> loader,
out T assembly) where T : class
{
assembly = null;
if (!Directory.Exists(directory))
return false;
var potentialDirectories = new List<string> { directory };
potentialDirectories.AddRange(Directory.GetDirectories(directory, "*", SearchOption.AllDirectories));
foreach (var subDirectory in potentialDirectories)
{
var path = Path.Combine(subDirectory, $"{assemblyName.Name}.dll");
if (!File.Exists(path))
continue;
try
{
assembly = loader(path);
}
catch (Exception)
{
continue;
}
return true;
}
return false;
}
/// <summary>
/// Try to resolve and load the given assembly DLL.
/// </summary>
/// <param name="assemblyName">Name of the assembly, of the type <see cref="AssemblyName" />.</param>
/// <param name="directory">Directory to search the assembly from.</param>
/// <param name="assembly">The loaded assembly.</param>
/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly) =>
TryResolveDllAssembly(assemblyName, directory, Assembly.LoadFile, out assembly);
}
}
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>067bcc8c-fc25-4929-b4f4-c6a5a7496994</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>BepInEx.NetLauncher.Shared</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)SharedEntrypoint.cs" />
</ItemGroup>
</Project>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>067bcc8c-fc25-4929-b4f4-c6a5a7496994</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="BepInEx.NetLauncher.Shared.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>
@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
// This code is shared between the .NET Framework launcher and the .NET Core entrypoint projects.
// However, it cannot be placed in the NetLauncher.Common project because this has to do with assembly resolution,
// and placing this logic inside of an external assembly will cause a crash.
// A shared project is the only way to deduplicate code here
namespace BepInEx.NetLauncher.Shared
{
internal static class SharedEntrypoint
{
public static ResolveEventHandler RemoteResolve(List<string> resolveDirectories)
{
return (sender, args) => RemoteResolveInternal(sender, args, resolveDirectories);
}
private static Assembly RemoteResolveInternal(object sender, ResolveEventArgs reference, List<string> resolveDirectories)
{
var assemblyName = new AssemblyName(reference.Name);
foreach (var directory in resolveDirectories)
{
if (!Directory.Exists(directory))
continue;
var potentialDirectories = new List<string> { directory };
potentialDirectories.AddRange(Directory.GetDirectories(directory, "*", SearchOption.AllDirectories));
var potentialFiles = potentialDirectories.Select(x => Path.Combine(x, $"{assemblyName.Name}.dll"))
.Concat(potentialDirectories.Select(x =>
Path
.Combine(x,
$"{assemblyName.Name}.exe")));
foreach (var path in potentialFiles)
{
if (!File.Exists(path))
continue;
Assembly assembly;
try
{
assembly = Assembly.LoadFrom(path);
}
catch (Exception ex)
{
continue;
}
if (assembly.GetName().Name == assemblyName.Name)
return assembly;
}
}
return null;
}
public static Assembly LocalResolve(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
var foundAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(x => x.GetName().Name == assemblyName.Name);
if (foundAssembly != null)
return foundAssembly;
if (LocalUtility.TryResolveDllAssembly(assemblyName, Paths.BepInExAssemblyDirectory, out foundAssembly)
|| LocalUtility.TryResolveDllAssembly(assemblyName, Paths.PatcherPluginPath, out foundAssembly)
|| LocalUtility.TryResolveDllAssembly(assemblyName, Paths.PluginPath, out foundAssembly))
return foundAssembly;
return null;
}
}
/// <summary>
/// Generic helper properties and methods.
/// </summary>
internal static class LocalUtility
{
/// <summary>
/// Try to resolve and load the given assembly DLL.
/// </summary>
/// <param name="assemblyName">Name of the assembly, of the type <see cref="AssemblyName" />.</param>
/// <param name="directory">Directory to search the assembly from.</param>
/// <param name="assembly">The loaded assembly.</param>
/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
private static bool TryResolveDllAssembly<T>(AssemblyName assemblyName,
string directory,
Func<string, T> loader,
out T assembly) where T : class
{
assembly = null;
if (!Directory.Exists(directory))
return false;
var potentialDirectories = new List<string> { directory };
potentialDirectories.AddRange(Directory.GetDirectories(directory, "*", SearchOption.AllDirectories));
foreach (var subDirectory in potentialDirectories)
{
var path = Path.Combine(subDirectory, $"{assemblyName.Name}.dll");
if (!File.Exists(path))
continue;
try
{
assembly = loader(path);
}
catch (Exception)
{
continue;
}
return true;
}
return false;
}
/// <summary>
/// Try to resolve and load the given assembly DLL.
/// </summary>
/// <param name="assemblyName">Name of the assembly, of the type <see cref="AssemblyName" />.</param>
/// <param name="directory">Directory to search the assembly from.</param>
/// <param name="assembly">The loaded assembly.</param>
/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly) =>
TryResolveDllAssembly(assemblyName, directory, Assembly.LoadFile, out assembly);
}
}
@@ -31,4 +31,5 @@
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="All" />
</ItemGroup>
<Import Project="..\BepInEx.Shared\BepInEx.Shared.projitems" Label="Shared" />
<Import Project="..\BepInEx.NetLauncher.Shared\BepInEx.NetLauncher.Shared.projitems" Label="Shared" />
</Project>
+31 -149
View File
@@ -2,63 +2,12 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using BepInEx.Logging;
using BepInEx.NetLauncher.Shared;
using BepInEx.Preloader.Core;
namespace BepInEx.NetLauncher;
internal static class NetPreloaderRunner
{
internal static void PreloaderMain(string[] args)
{
try
{
Logger.Listeners.Add(new ConsoleLogListener());
ConsoleManager.Initialize(true, false);
NetPreloader.Start(args);
}
catch (Exception ex)
{
PreloaderLogger.Log.Log(LogLevel.Fatal, "Unhandled exception");
PreloaderLogger.Log.Log(LogLevel.Fatal, ex);
Program.ReadExit();
}
}
internal static void OuterMain(string[] args, string filename)
{
PlatformUtils.SetPlatform();
Paths.SetExecutablePath(filename);
AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
PreloaderMain(args);
}
private static Assembly LocalResolve(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
var foundAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(x => x.GetName().Name == assemblyName.Name);
if (foundAssembly != null)
return foundAssembly;
if (LocalUtility.TryResolveDllAssembly(assemblyName, Paths.BepInExAssemblyDirectory, out foundAssembly)
|| LocalUtility.TryResolveDllAssembly(assemblyName, Paths.PatcherPluginPath, out foundAssembly)
|| LocalUtility.TryResolveDllAssembly(assemblyName, Paths.PluginPath, out foundAssembly))
return foundAssembly;
return null;
}
}
internal class Program
{
public static List<string> ResolveDirectories { get; set; } = new()
@@ -75,6 +24,8 @@ internal class Program
private static void Main(string[] args)
{
var silentExceptionLog = $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log";
try
{
string filename;
@@ -88,122 +39,53 @@ internal class Program
// for debugging within VS
ResolveDirectories.Add(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
#else
filename = Process.GetCurrentProcess().MainModule.FileName;
ResolveDirectories.Add(Path.Combine(Path.GetDirectoryName(filename), "BepInEx", "core"));
filename = Process.GetCurrentProcess().MainModule.FileName;
ResolveDirectories.Add(Path.Combine(Path.GetDirectoryName(filename), "BepInEx", "core"));
#endif
AppDomain.CurrentDomain.AssemblyResolve += RemoteResolve;
AppDomain.CurrentDomain.AssemblyResolve += SharedEntrypoint.RemoteResolve(ResolveDirectories);
NetPreloaderRunner.OuterMain(args, filename);
}
catch (Exception ex)
{
File.WriteAllText(silentExceptionLog, ex.ToString());
Console.WriteLine("Unhandled exception");
Console.WriteLine(ex);
ReadExit();
}
}
private static Assembly RemoteResolve(object sender, ResolveEventArgs reference)
{
var assemblyName = new AssemblyName(reference.Name);
foreach (var directory in ResolveDirectories)
{
if (!Directory.Exists(directory))
continue;
var potentialDirectories = new List<string> { directory };
potentialDirectories.AddRange(Directory.GetDirectories(directory, "*", SearchOption.AllDirectories));
var potentialFiles = potentialDirectories.Select(x => Path.Combine(x, $"{assemblyName.Name}.dll"))
.Concat(potentialDirectories.Select(x =>
Path
.Combine(x,
$"{assemblyName.Name}.exe")));
foreach (var path in potentialFiles)
{
if (!File.Exists(path))
continue;
Assembly assembly;
try
{
assembly = Assembly.LoadFrom(path);
}
catch (Exception ex)
{
continue;
}
if (assembly.GetName().Name == assemblyName.Name)
return assembly;
}
}
return null;
}
}
/// <summary>
/// Generic helper properties and methods.
/// </summary>
public static class LocalUtility
internal static class NetPreloaderRunner
{
/// <summary>
/// Try to resolve and load the given assembly DLL.
/// </summary>
/// <param name="assemblyName">Name of the assembly, of the type <see cref="AssemblyName" />.</param>
/// <param name="directory">Directory to search the assembly from.</param>
/// <param name="assembly">The loaded assembly.</param>
/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
private static bool TryResolveDllAssembly<T>(AssemblyName assemblyName,
string directory,
Func<string, T> loader,
out T assembly) where T : class
internal static void PreloaderMain(string[] args)
{
assembly = null;
Logger.Listeners.Add(new ConsoleLogListener());
if (!Directory.Exists(directory))
return false;
ConsoleManager.Initialize(true, false);
var potentialDirectories = new List<string> { directory };
potentialDirectories.AddRange(Directory.GetDirectories(directory, "*", SearchOption.AllDirectories));
foreach (var subDirectory in potentialDirectories)
try
{
var path = Path.Combine(subDirectory, $"{assemblyName.Name}.dll");
if (!File.Exists(path))
continue;
try
{
assembly = loader(path);
}
catch (Exception)
{
continue;
}
return true;
NetPreloader.Start(args);
}
catch (Exception ex)
{
PreloaderLogger.Log.Log(LogLevel.Fatal, "Unhandled exception");
PreloaderLogger.Log.Log(LogLevel.Fatal, ex);
Program.ReadExit();
}
return false;
}
/// <summary>
/// Try to resolve and load the given assembly DLL.
/// </summary>
/// <param name="assemblyName">Name of the assembly, of the type <see cref="AssemblyName" />.</param>
/// <param name="directory">Directory to search the assembly from.</param>
/// <param name="assembly">The loaded assembly.</param>
/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly) =>
TryResolveDllAssembly(assemblyName, directory, Assembly.LoadFile, out assembly);
internal static void OuterMain(string[] args, string filename)
{
PlatformUtils.SetPlatform();
Paths.SetExecutablePath(filename);
AppDomain.CurrentDomain.AssemblyResolve += SharedEntrypoint.LocalResolve;
PreloaderMain(args);
}
}
+8 -1
View File
@@ -35,12 +35,18 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "BepInEx.Shared", "BepInEx.S
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BepInEx.NetLauncher.Common", "BepInEx.NetLauncher.Common\BepInEx.NetLauncher.Common.csproj", "{76EB1952-7826-471C-9CF3-815D094DA077}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.NetCore", "BepInEx.NetCore\BepInEx.NetCore.csproj", "{08083F40-98AA-4176-BA01-817C18C35C7F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BepInEx.NetCore", "BepInEx.NetCore\BepInEx.NetCore.csproj", "{08083F40-98AA-4176-BA01-817C18C35C7F}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "BepInEx.NetLauncher.Shared", "BepInEx.NetLauncher.Shared\BepInEx.NetLauncher.Shared.shproj", "{067BCC8C-FC25-4929-B4F4-C6A5A7496994}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
BepInEx.NetLauncher.Shared\BepInEx.NetLauncher.Shared.projitems*{067bcc8c-fc25-4929-b4f4-c6a5a7496994}*SharedItemsImports = 13
BepInEx.NetLauncher.Shared\BepInEx.NetLauncher.Shared.projitems*{08083f40-98aa-4176-ba01-817c18c35c7f}*SharedItemsImports = 5
BepInEx.Shared\BepInEx.Shared.projitems*{08083f40-98aa-4176-ba01-817c18c35c7f}*SharedItemsImports = 5
BepInEx.Shared\BepInEx.Shared.projitems*{15f8bc38-a761-4f93-8903-1b531ac5d9f9}*SharedItemsImports = 5
BepInEx.Shared\BepInEx.Shared.projitems*{429d5f83-7fec-4d09-ab82-e868eb61af59}*SharedItemsImports = 5
BepInEx.NetLauncher.Shared\BepInEx.NetLauncher.Shared.projitems*{490b052b-7f23-4052-ad04-613856618901}*SharedItemsImports = 5
BepInEx.Shared\BepInEx.Shared.projitems*{490b052b-7f23-4052-ad04-613856618901}*SharedItemsImports = 5
BepInEx.Shared\BepInEx.Shared.projitems*{4ffba620-f5ed-47f9-b90c-dad1316fd9b9}*SharedItemsImports = 5
BepInEx.Shared\BepInEx.Shared.projitems*{5c7847fb-c16d-47bd-bb17-1045669f9aa7}*SharedItemsImports = 13
@@ -131,6 +137,7 @@ Global
{429D5F83-7FEC-4D09-AB82-E868EB61AF59} = {D5F47CFC-E4F3-46EE-B4F3-2474CF7D9E2C}
{76EB1952-7826-471C-9CF3-815D094DA077} = {6EC98884-A3DC-4828-85EF-5F10F7519429}
{08083F40-98AA-4176-BA01-817C18C35C7F} = {6EC98884-A3DC-4828-85EF-5F10F7519429}
{067BCC8C-FC25-4929-B4F4-C6A5A7496994} = {6EC98884-A3DC-4828-85EF-5F10F7519429}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {55AC11EF-F568-4C79-A356-7ED9510145B1}