From 5686f6a00309aa491ca2da5f64a015e4bda39b42 Mon Sep 17 00:00:00 2001 From: Bepis <36346617+bbepis@users.noreply.github.com> Date: Tue, 25 Jan 2022 21:11:55 +1100 Subject: [PATCH] Create .NET Core platform project --- BepInEx.Core/Console/ConsoleManager.cs | 6 +- BepInEx.Core/Console/IConsoleDriver.cs | 4 +- .../Console/Unix/LinuxConsoleDriver.cs | 4 +- .../Console/Windows/WindowsConsoleDriver.cs | 30 ++- BepInEx.Core/Properties/AssemblyInfo.cs | 3 +- BepInEx.IL2CPP/Preloader.cs | 2 +- BepInEx.NetCore/BepInEx.NetCore.csproj | 20 ++ BepInEx.NetCore/HookEntrypoint.cs | 202 ++++++++++++++++++ BepInEx.NetCore/NetCorePreloader.cs | 68 ++++++ BepInEx.NetLauncher.Common/BasePlugin.cs | 30 +++ .../BepInEx.NetLauncher.Common.csproj | 16 ++ BepInEx.NetLauncher.Common/NetChainloader.cs | 28 +++ BepInEx.NetLauncher/BasePlugin.cs | 29 --- .../BepInEx.NetLauncher.csproj | 13 +- BepInEx.NetLauncher/NetChainloader.cs | 27 --- BepInEx.NetLauncher/NetPreloader.cs | 1 + BepInEx.NetLauncher/Program.cs | 61 +++++- BepInEx.NetLauncher/Utility.cs | 65 ------ BepInEx.Preloader.Core/AssemblyBuildInfo.cs | 64 ++++-- .../Properties/AssemblyInfo.cs | 3 +- BepInEx.Preloader.Unity/UnityPreloader.cs | 2 +- BepInEx.sln | 32 ++- 22 files changed, 546 insertions(+), 164 deletions(-) create mode 100644 BepInEx.NetCore/BepInEx.NetCore.csproj create mode 100644 BepInEx.NetCore/HookEntrypoint.cs create mode 100644 BepInEx.NetCore/NetCorePreloader.cs create mode 100644 BepInEx.NetLauncher.Common/BasePlugin.cs create mode 100644 BepInEx.NetLauncher.Common/BepInEx.NetLauncher.Common.csproj create mode 100644 BepInEx.NetLauncher.Common/NetChainloader.cs delete mode 100644 BepInEx.NetLauncher/BasePlugin.cs delete mode 100644 BepInEx.NetLauncher/NetChainloader.cs delete mode 100644 BepInEx.NetLauncher/Utility.cs diff --git a/BepInEx.Core/Console/ConsoleManager.cs b/BepInEx.Core/Console/ConsoleManager.cs index 9470aec..74f2bca 100644 --- a/BepInEx.Core/Console/ConsoleManager.cs +++ b/BepInEx.Core/Console/ConsoleManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.IO; using System.Text; @@ -94,7 +94,7 @@ public static class ConsoleManager public static TextWriter ConsoleStream => Driver?.ConsoleOut; - public static void Initialize(bool alreadyActive) + public static void Initialize(bool alreadyActive, bool useWinApiEncoder) { if (PlatformHelper.Is(Platform.Unix)) Driver = new LinuxConsoleDriver(); @@ -104,7 +104,7 @@ public static class ConsoleManager throw new PlatformNotSupportedException("Was unable to determine console driver for platform " + PlatformHelper.Current); - Driver.Initialize(alreadyActive); + Driver.Initialize(alreadyActive, useWinApiEncoder); } private static void DriverCheck() diff --git a/BepInEx.Core/Console/IConsoleDriver.cs b/BepInEx.Core/Console/IConsoleDriver.cs index 0015610..2cdc38c 100644 --- a/BepInEx.Core/Console/IConsoleDriver.cs +++ b/BepInEx.Core/Console/IConsoleDriver.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; namespace BepInEx; @@ -13,7 +13,7 @@ internal interface IConsoleDriver void PreventClose(); - void Initialize(bool alreadyActive); + void Initialize(bool alreadyActive, bool useWinApiEncoder); // Apparently Windows code-pages work in Mono. // https://stackoverflow.com/a/33456543 diff --git a/BepInEx.Core/Console/Unix/LinuxConsoleDriver.cs b/BepInEx.Core/Console/Unix/LinuxConsoleDriver.cs index de32fe0..afa0929 100644 --- a/BepInEx.Core/Console/Unix/LinuxConsoleDriver.cs +++ b/BepInEx.Core/Console/Unix/LinuxConsoleDriver.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using BepInEx.Configuration; using BepInEx.Logging; @@ -46,7 +46,7 @@ internal class LinuxConsoleDriver : IConsoleDriver // Not supported by all distros } - public void Initialize(bool alreadyActive) + public void Initialize(bool alreadyActive, bool useWinApiEncoder) { // Console is always considered active on Unix ConsoleActive = true; diff --git a/BepInEx.Core/Console/Windows/WindowsConsoleDriver.cs b/BepInEx.Core/Console/Windows/WindowsConsoleDriver.cs index 7608e35..ec8fef0 100644 --- a/BepInEx.Core/Console/Windows/WindowsConsoleDriver.cs +++ b/BepInEx.Core/Console/Windows/WindowsConsoleDriver.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Reflection; @@ -33,15 +33,37 @@ internal class WindowsConsoleDriver : IConsoleDriver private int ConsoleWidth => getWindowWidth?.Invoke() ?? 0; private int ConsoleHeight => getWindowHeight?.Invoke() ?? 0; + private bool useWinApiEncoder; + public TextWriter StandardOut { get; private set; } public TextWriter ConsoleOut { get; private set; } public bool ConsoleActive { get; private set; } public bool ConsoleIsExternal => true; - public void Initialize(bool alreadyActive) + private bool TryCheckConsoleExists() { - ConsoleActive = alreadyActive || ConsoleWidth != 0 && ConsoleHeight != 0; + try + { + return ConsoleWidth != 0 && ConsoleHeight != 0; + } + catch (IOException) + { + //System.IO.IOException: The handle is invalid. + // at System.ConsolePal.GetBufferInfo(Boolean throwOnNoConsole, Boolean & succeeded) + //at System.ConsolePal.get_WindowWidth() + //at System.Console.get_WindowWidth() + //at BepInEx.WindowsConsoleDriver.get_ConsoleWidth() + //at BepInEx.WindowsConsoleDriver.Initialize(Boolean alreadyActive) + + return false; + } + } + + public void Initialize(bool alreadyActive, bool useWinApiEncoder) + { + ConsoleActive = alreadyActive || TryCheckConsoleExists(); + this.useWinApiEncoder = useWinApiEncoder; if (ConsoleActive) { @@ -82,7 +104,7 @@ internal class WindowsConsoleDriver : IConsoleDriver var consoleOutStream = OpenFileStream(ConsoleWindow.ConsoleOutHandle); // Can't use Console.OutputEncoding because it can be null (i.e. not preference by user) - ConsoleOut = new StreamWriter(consoleOutStream, ConsoleEncoding.OutputEncoding) + ConsoleOut = new StreamWriter(consoleOutStream, useWinApiEncoder ? ConsoleEncoding.OutputEncoding : Utility.UTF8NoBom) { AutoFlush = true }; diff --git a/BepInEx.Core/Properties/AssemblyInfo.cs b/BepInEx.Core/Properties/AssemblyInfo.cs index 8e90f0f..817bf39 100644 --- a/BepInEx.Core/Properties/AssemblyInfo.cs +++ b/BepInEx.Core/Properties/AssemblyInfo.cs @@ -1,7 +1,8 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("BepInEx.Preloader.Core")] [assembly: InternalsVisibleTo("BepInEx.Unity")] [assembly: InternalsVisibleTo("BepInEx.NetLauncher")] +[assembly: InternalsVisibleTo("BepInEx.NetCore")] [assembly: InternalsVisibleTo("BepInEx.IL2CPP")] [assembly: InternalsVisibleTo("BepInExTests")] diff --git a/BepInEx.IL2CPP/Preloader.cs b/BepInEx.IL2CPP/Preloader.cs index d907716..b01a69b 100644 --- a/BepInEx.IL2CPP/Preloader.cs +++ b/BepInEx.IL2CPP/Preloader.cs @@ -41,7 +41,7 @@ public static class Preloader { HarmonyBackendFix.Initialize(); - ConsoleManager.Initialize(false); + ConsoleManager.Initialize(false, false); PreloaderLog = new PreloaderConsoleListener(); Logger.Listeners.Add(PreloaderLog); diff --git a/BepInEx.NetCore/BepInEx.NetCore.csproj b/BepInEx.NetCore/BepInEx.NetCore.csproj new file mode 100644 index 0000000..f1d1329 --- /dev/null +++ b/BepInEx.NetCore/BepInEx.NetCore.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + + + 10 + + + + 10 + + + + + + + + diff --git a/BepInEx.NetCore/HookEntrypoint.cs b/BepInEx.NetCore/HookEntrypoint.cs new file mode 100644 index 0000000..a2befd8 --- /dev/null +++ b/BepInEx.NetCore/HookEntrypoint.cs @@ -0,0 +1,202 @@ +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.Preloader.Core; + +internal class StartupHook +{ + public static List ResolveDirectories = new(); + + public static void Initialize() + { + try + { + string filename; + +//#if DEBUG +// filename = +// Path.Combine(Directory.GetCurrentDirectory(), +// Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName)); +// ResolveDirectories.Add(Path.GetDirectoryName(filename)); + +// // 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")); +//#endif + + + AppDomain.CurrentDomain.AssemblyResolve += RemoteResolve; + + NetCorePreloaderRunner.OuterMain(filename); + } + catch (Exception ex) + { + 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 { 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 +{ + internal static class NetCorePreloaderRunner + { + internal static void PreloaderMain() + { + try + { + ConsoleManager.Initialize(false, false); + + ConsoleManager.CreateConsole(); + + Logger.Listeners.Add(new ConsoleLogListener()); + + NetCorePreloader.Start(); + } + catch (Exception ex) + { + PreloaderLogger.Log.Log(LogLevel.Fatal, "Unhandled exception"); + PreloaderLogger.Log.Log(LogLevel.Fatal, ex); + } + } + + internal static void OuterMain(string filename) + { + PlatformUtils.SetPlatform(); + + Paths.SetExecutablePath(filename); + + AppDomain.CurrentDomain.AssemblyResolve += 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; + } + } + + /// + /// Generic helper properties and methods. + /// + public static class LocalUtility + { + /// + /// Try to resolve and load the given assembly DLL. + /// + /// Name of the assembly, of the type . + /// Directory to search the assembly from. + /// The loaded assembly. + /// True, if the assembly was found and loaded. Otherwise, false. + private static bool TryResolveDllAssembly(AssemblyName assemblyName, + string directory, + Func loader, + out T assembly) where T : class + { + assembly = null; + + if (!Directory.Exists(directory)) + return false; + + var potentialDirectories = new List { 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; + } + + /// + /// Try to resolve and load the given assembly DLL. + /// + /// Name of the assembly, of the type . + /// Directory to search the assembly from. + /// The loaded assembly. + /// True, if the assembly was found and loaded. Otherwise, false. + public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly) => + TryResolveDllAssembly(assemblyName, directory, Assembly.LoadFile, out assembly); + } + +} diff --git a/BepInEx.NetCore/NetCorePreloader.cs b/BepInEx.NetCore/NetCorePreloader.cs new file mode 100644 index 0000000..b9e9ce9 --- /dev/null +++ b/BepInEx.NetCore/NetCorePreloader.cs @@ -0,0 +1,68 @@ +using System; +using System.Diagnostics; +using System.IO; +using BepInEx.Bootstrap; +using BepInEx.Logging; +using BepInEx.NetLauncher.Common; +using BepInEx.Preloader.Core; +using BepInEx.Preloader.Core.Logging; +using BepInEx.Preloader.Core.Patching; +using Mono.Cecil; + +namespace BepInEx.NetCore +{ + internal class NetCorePreloader + { + private static readonly ManualLogSource Log = PreloaderLogger.Log; + + public static void Start() + { + var preloaderListener = new PreloaderConsoleListener(); + Logger.Listeners.Add(preloaderListener); + + var entrypointAssemblyPath = Path.ChangeExtension(Process.GetCurrentProcess().MainModule.FileName, "dll"); + + Paths.SetExecutablePath(entrypointAssemblyPath); + + TypeLoader.SearchDirectories.Add(Path.GetDirectoryName(entrypointAssemblyPath)); + + Logger.Sources.Add(TraceLogSource.CreateSource()); + + ChainloaderLogHelper.PrintLogInfo(Log); + + Log.Log(LogLevel.Info, $"CLR runtime version: {Environment.Version}"); + + + AssemblyBuildInfo executableInfo; + + using (var entrypointAssembly = AssemblyDefinition.ReadAssembly(entrypointAssemblyPath)) + executableInfo = AssemblyBuildInfo.DetermineInfo(entrypointAssembly); + + Log.LogInfo($"Game executable build architecture: {executableInfo}"); + + Log.Log(LogLevel.Message, "Preloader started"); + + using (var assemblyPatcher = new AssemblyPatcher()) + { + assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath); + + Log.Log(LogLevel.Info, + $"{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"); + + assemblyPatcher.PatchAndLoad(); + } + + Log.LogMessage("Preloader finished"); + + Logger.Listeners.Remove(preloaderListener); + + var chainloader = new NetChainloader(); + chainloader.Initialize(); + chainloader.Execute(); + } + } +} diff --git a/BepInEx.NetLauncher.Common/BasePlugin.cs b/BepInEx.NetLauncher.Common/BasePlugin.cs new file mode 100644 index 0000000..14c8a1c --- /dev/null +++ b/BepInEx.NetLauncher.Common/BasePlugin.cs @@ -0,0 +1,30 @@ +using BepInEx.Configuration; +using BepInEx.Logging; +using HarmonyLib; + +namespace BepInEx.NetLauncher.Common +{ + public abstract class BasePlugin + { + protected BasePlugin() + { + var metadata = MetadataHelper.GetMetadata(this); + + HarmonyInstance = new Harmony("BepInEx.Plugin." + metadata.GUID); + + Log = Logger.CreateLogSource(metadata.Name); + + Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, metadata.GUID + ".cfg"), false, metadata); + } + + public ManualLogSource Log { get; } + + public ConfigFile Config { get; } + + public Harmony HarmonyInstance { get; set; } + + public abstract void Load(); + + public virtual bool Unload() => false; + } +} diff --git a/BepInEx.NetLauncher.Common/BepInEx.NetLauncher.Common.csproj b/BepInEx.NetLauncher.Common/BepInEx.NetLauncher.Common.csproj new file mode 100644 index 0000000..436d2e0 --- /dev/null +++ b/BepInEx.NetLauncher.Common/BepInEx.NetLauncher.Common.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.1;net40 + + + + + + + + + + + + diff --git a/BepInEx.NetLauncher.Common/NetChainloader.cs b/BepInEx.NetLauncher.Common/NetChainloader.cs new file mode 100644 index 0000000..5d8a2a6 --- /dev/null +++ b/BepInEx.NetLauncher.Common/NetChainloader.cs @@ -0,0 +1,28 @@ +using System; +using System.Reflection; +using BepInEx.Bootstrap; +using BepInEx.Preloader.Core.Logging; + +namespace BepInEx.NetLauncher.Common +{ + public class NetChainloader : BaseChainloader + { + public override BasePlugin LoadPlugin(PluginInfo pluginInfo, Assembly pluginAssembly) + { + var type = pluginAssembly.GetType(pluginInfo.TypeName); + + var pluginInstance = (BasePlugin)Activator.CreateInstance(type); + + pluginInstance.Load(); + + return pluginInstance; + } + + protected override void InitializeLoggers() + { + base.InitializeLoggers(); + + ChainloaderLogHelper.RewritePreloaderLogs(); + } + } +} diff --git a/BepInEx.NetLauncher/BasePlugin.cs b/BepInEx.NetLauncher/BasePlugin.cs deleted file mode 100644 index c2329d7..0000000 --- a/BepInEx.NetLauncher/BasePlugin.cs +++ /dev/null @@ -1,29 +0,0 @@ -using BepInEx.Configuration; -using BepInEx.Logging; -using HarmonyLib; - -namespace BepInEx.NetLauncher; - -public abstract class BasePlugin -{ - protected BasePlugin() - { - var metadata = MetadataHelper.GetMetadata(this); - - HarmonyInstance = new Harmony("BepInEx.Plugin." + metadata.GUID); - - Log = Logger.CreateLogSource(metadata.Name); - - Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, metadata.GUID + ".cfg"), false, metadata); - } - - public ManualLogSource Log { get; } - - public ConfigFile Config { get; } - - public Harmony HarmonyInstance { get; set; } - - public abstract void Load(); - - public virtual bool Unload() => false; -} diff --git a/BepInEx.NetLauncher/BepInEx.NetLauncher.csproj b/BepInEx.NetLauncher/BepInEx.NetLauncher.csproj index cf918bd..5326456 100644 --- a/BepInEx.NetLauncher/BepInEx.NetLauncher.csproj +++ b/BepInEx.NetLauncher/BepInEx.NetLauncher.csproj @@ -17,17 +17,18 @@ portable ..\bin\NetLauncher\ - + x86 true - - + + + - - + + - + diff --git a/BepInEx.NetLauncher/NetChainloader.cs b/BepInEx.NetLauncher/NetChainloader.cs deleted file mode 100644 index 273597d..0000000 --- a/BepInEx.NetLauncher/NetChainloader.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Reflection; -using BepInEx.Bootstrap; -using BepInEx.Preloader.Core.Logging; - -namespace BepInEx.NetLauncher; - -public class NetChainloader : BaseChainloader -{ - public override BasePlugin LoadPlugin(PluginInfo pluginInfo, Assembly pluginAssembly) - { - var type = pluginAssembly.GetType(pluginInfo.TypeName); - - var pluginInstance = (BasePlugin) Activator.CreateInstance(type); - - pluginInstance.Load(); - - return pluginInstance; - } - - protected override void InitializeLoggers() - { - base.InitializeLoggers(); - - ChainloaderLogHelper.RewritePreloaderLogs(); - } -} diff --git a/BepInEx.NetLauncher/NetPreloader.cs b/BepInEx.NetLauncher/NetPreloader.cs index f5ede02..60bbcf4 100644 --- a/BepInEx.NetLauncher/NetPreloader.cs +++ b/BepInEx.NetLauncher/NetPreloader.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; +using BepInEx.NetLauncher.Common; using BepInEx.NetLauncher.RuntimeFixes; using BepInEx.Preloader.Core; using BepInEx.Preloader.Core.Logging; diff --git a/BepInEx.NetLauncher/Program.cs b/BepInEx.NetLauncher/Program.cs index b03dd86..cb767e3 100644 --- a/BepInEx.NetLauncher/Program.cs +++ b/BepInEx.NetLauncher/Program.cs @@ -17,7 +17,7 @@ internal static class NetPreloaderRunner { Logger.Listeners.Add(new ConsoleLogListener()); - ConsoleManager.Initialize(true); + ConsoleManager.Initialize(true, false); NetPreloader.Start(args); } @@ -148,3 +148,62 @@ internal class Program return null; } } + +/// +/// Generic helper properties and methods. +/// +public static class LocalUtility +{ + /// + /// Try to resolve and load the given assembly DLL. + /// + /// Name of the assembly, of the type . + /// Directory to search the assembly from. + /// The loaded assembly. + /// True, if the assembly was found and loaded. Otherwise, false. + private static bool TryResolveDllAssembly(AssemblyName assemblyName, + string directory, + Func loader, + out T assembly) where T : class + { + assembly = null; + + if (!Directory.Exists(directory)) + return false; + + var potentialDirectories = new List { 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; + } + + /// + /// Try to resolve and load the given assembly DLL. + /// + /// Name of the assembly, of the type . + /// Directory to search the assembly from. + /// The loaded assembly. + /// True, if the assembly was found and loaded. Otherwise, false. + public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly) => + TryResolveDllAssembly(assemblyName, directory, Assembly.LoadFile, out assembly); +} diff --git a/BepInEx.NetLauncher/Utility.cs b/BepInEx.NetLauncher/Utility.cs deleted file mode 100644 index a062eed..0000000 --- a/BepInEx.NetLauncher/Utility.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; - -namespace BepInEx.NetLauncher; - -/// -/// Generic helper properties and methods. -/// -public static class LocalUtility -{ - /// - /// Try to resolve and load the given assembly DLL. - /// - /// Name of the assembly, of the type . - /// Directory to search the assembly from. - /// The loaded assembly. - /// True, if the assembly was found and loaded. Otherwise, false. - private static bool TryResolveDllAssembly(AssemblyName assemblyName, - string directory, - Func loader, - out T assembly) where T : class - { - assembly = null; - - if (!Directory.Exists(directory)) - return false; - - var potentialDirectories = new List { 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; - } - - /// - /// Try to resolve and load the given assembly DLL. - /// - /// Name of the assembly, of the type . - /// Directory to search the assembly from. - /// The loaded assembly. - /// True, if the assembly was found and loaded. Otherwise, false. - public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly) => - TryResolveDllAssembly(assemblyName, directory, Assembly.LoadFile, out assembly); -} diff --git a/BepInEx.Preloader.Core/AssemblyBuildInfo.cs b/BepInEx.Preloader.Core/AssemblyBuildInfo.cs index 38ca1a0..a6477ca 100644 --- a/BepInEx.Preloader.Core/AssemblyBuildInfo.cs +++ b/BepInEx.Preloader.Core/AssemblyBuildInfo.cs @@ -6,43 +6,66 @@ namespace BepInEx.Preloader.Core { public class AssemblyBuildInfo { + public enum FrameworkType + { + Unknown, + NetFramework, + NetStandard, + NetCore + } + public Version NetFrameworkVersion { get; private set; } public bool IsAnyCpu { get; set; } public bool Is64Bit { get; set; } - private static Version GetNet4Version(AssemblyDefinition assemblyDefinition) + public FrameworkType AssemblyFrameworkType { get; set; } + + private void SetNet4Version(AssemblyDefinition assemblyDefinition) { + NetFrameworkVersion = new Version(0, 0); + AssemblyFrameworkType = FrameworkType.Unknown; + var targetFrameworkAttribute = assemblyDefinition.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.Versioning.TargetFrameworkAttribute"); if (targetFrameworkAttribute == null) - return null; + return; if (targetFrameworkAttribute.ConstructorArguments.Count < 1) - return null; + return; if (targetFrameworkAttribute.ConstructorArguments[0].Type.Name != "String") - return null; + return; string versionInfo = (string)targetFrameworkAttribute.ConstructorArguments[0].Value; var values = versionInfo.Split(','); + + foreach (var value in values) { - if (!value.StartsWith("Version=v")) - continue; - - try + if (value.StartsWith(".NET")) { - return new Version(value.Substring("Version=v".Length)); + AssemblyFrameworkType = value switch + { + ".NETFramework" => FrameworkType.NetFramework, + ".NETCoreApp" => FrameworkType.NetCore, + ".NETStandard" => FrameworkType.NetStandard, + _ => FrameworkType.Unknown + }; + } + else if (value.StartsWith("Version=v")) + { + try + { + NetFrameworkVersion = new Version(value.Substring("Version=v".Length)); + } + catch { } } - catch {} } - - return null; } public static AssemblyBuildInfo DetermineInfo(AssemblyDefinition assemblyDefinition) @@ -56,20 +79,23 @@ namespace BepInEx.Preloader.Core if (runtime == TargetRuntime.Net_1_0) { buildInfo.NetFrameworkVersion = new Version(1, 0); + buildInfo.AssemblyFrameworkType = FrameworkType.NetFramework; } else if (runtime == TargetRuntime.Net_1_1) { buildInfo.NetFrameworkVersion = new Version(1, 1); + buildInfo.AssemblyFrameworkType = FrameworkType.NetFramework; } else if (runtime == TargetRuntime.Net_2_0) { // Assume 3.5 here. The code to determine versions between 2.0 and 3.5 is not worth the amount of non-unity games that use it, if any buildInfo.NetFrameworkVersion = new Version(3, 5); + buildInfo.AssemblyFrameworkType = FrameworkType.NetFramework; } else { - buildInfo.NetFrameworkVersion = GetNet4Version(assemblyDefinition) ?? new Version(4, 0); + buildInfo.SetNet4Version(assemblyDefinition); } // bitness @@ -131,12 +157,20 @@ namespace BepInEx.Preloader.Core /// public override string ToString() { + string frameworkType = AssemblyFrameworkType switch { + FrameworkType.NetFramework => "Framework", + FrameworkType.NetStandard => "Standard", + FrameworkType.NetCore => "Core", + FrameworkType.Unknown => "Unknown", + _ => throw new ArgumentOutOfRangeException() + }; + if (IsAnyCpu) { - return $".NET Framework {NetFrameworkVersion}, AnyCPU ({(Is64Bit ? "64" : "32")}-bit preferred)"; + return $".NET {frameworkType} {NetFrameworkVersion}, AnyCPU ({(Is64Bit ? "64" : "32")}-bit preferred)"; } - return $".NET Framework {NetFrameworkVersion}, {(Is64Bit ? "x64" : "x86")}"; + return $".NET {frameworkType} {NetFrameworkVersion}, {(Is64Bit ? "x64" : "x86")}"; } } } diff --git a/BepInEx.Preloader.Core/Properties/AssemblyInfo.cs b/BepInEx.Preloader.Core/Properties/AssemblyInfo.cs index 86fd66a..6fdd874 100644 --- a/BepInEx.Preloader.Core/Properties/AssemblyInfo.cs +++ b/BepInEx.Preloader.Core/Properties/AssemblyInfo.cs @@ -1,5 +1,6 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("BepInEx.Preloader.Unity")] [assembly: InternalsVisibleTo("BepInEx.NetLauncher")] +[assembly: InternalsVisibleTo("BepInEx.NetCore")] [assembly: InternalsVisibleTo("BepInEx.IL2CPP")] diff --git a/BepInEx.Preloader.Unity/UnityPreloader.cs b/BepInEx.Preloader.Unity/UnityPreloader.cs index 63470ad..be7798f 100644 --- a/BepInEx.Preloader.Unity/UnityPreloader.cs +++ b/BepInEx.Preloader.Unity/UnityPreloader.cs @@ -43,7 +43,7 @@ internal static class UnityPreloader { HarmonyBackendFix.Initialize(); - ConsoleManager.Initialize(false); + ConsoleManager.Initialize(false, true); AllocateConsole(); Utility.TryDo(() => diff --git a/BepInEx.sln b/BepInEx.sln index 95cdac7..62ce6c4 100644 --- a/BepInEx.sln +++ b/BepInEx.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28922.388 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BepInExTests", "BepInExTests\BepInExTests.csproj", "{E7CD429A-D057-48E3-8C51-E5C934E8E07B}" ProjectSection(ProjectDependencies) = postProject @@ -26,23 +26,25 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IL2CPP", "IL2CPP", "{D5F47C EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BepInEx.IL2CPP", "BepInEx.IL2CPP\BepInEx.IL2CPP.csproj", "{429D5F83-7FEC-4D09-AB82-E868EB61AF59}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Assets", "Assets", "{68C57430-3E92-4B50-A9A5-208FE9CCD914}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Markdown", "Markdown", "{68C57430-3E92-4B50-A9A5-208FE9CCD914}" ProjectSection(SolutionItems) = preProject README.md = README.md - build.cake = build.cake EndProjectSection EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "BepInEx.Shared", "BepInEx.Shared\BepInEx.Shared.shproj", "{5C7847FB-C16D-47BD-BB17-1045669F9AA7}" 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}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution + 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.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 - BepInEx.Shared\BepInEx.Shared.projitems*{6e6bc1e5-5be8-4566-b3ae-52c4cb218aeb}*SharedItemsImports = 5 BepInEx.Shared\BepInEx.Shared.projitems*{d404c973-441d-48ed-b266-c21320ba0d87}*SharedItemsImports = 5 - BepInEx.Shared\BepInEx.Shared.projitems*{dc89f18b-235b-4c01-ab31-af40dce5c4c7}*SharedItemsImports = 5 BepInEx.Shared\BepInEx.Shared.projitems*{eae9fae6-8011-45a3-8b6e-0c7f14210533}*SharedItemsImports = 5 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -102,6 +104,22 @@ Global {429D5F83-7FEC-4D09-AB82-E868EB61AF59}.Release_Unity|Any CPU.Build.0 = Release|Any CPU {429D5F83-7FEC-4D09-AB82-E868EB61AF59}.Release|Any CPU.ActiveCfg = Release|Any CPU {429D5F83-7FEC-4D09-AB82-E868EB61AF59}.Release|Any CPU.Build.0 = Release|Any CPU + {76EB1952-7826-471C-9CF3-815D094DA077}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {76EB1952-7826-471C-9CF3-815D094DA077}.Debug|Any CPU.Build.0 = Debug|Any CPU + {76EB1952-7826-471C-9CF3-815D094DA077}.Release_NetFramework|Any CPU.ActiveCfg = Release|Any CPU + {76EB1952-7826-471C-9CF3-815D094DA077}.Release_NetFramework|Any CPU.Build.0 = Release|Any CPU + {76EB1952-7826-471C-9CF3-815D094DA077}.Release_Unity|Any CPU.ActiveCfg = Release|Any CPU + {76EB1952-7826-471C-9CF3-815D094DA077}.Release_Unity|Any CPU.Build.0 = Release|Any CPU + {76EB1952-7826-471C-9CF3-815D094DA077}.Release|Any CPU.ActiveCfg = Release|Any CPU + {76EB1952-7826-471C-9CF3-815D094DA077}.Release|Any CPU.Build.0 = Release|Any CPU + {08083F40-98AA-4176-BA01-817C18C35C7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08083F40-98AA-4176-BA01-817C18C35C7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08083F40-98AA-4176-BA01-817C18C35C7F}.Release_NetFramework|Any CPU.ActiveCfg = Release|Any CPU + {08083F40-98AA-4176-BA01-817C18C35C7F}.Release_NetFramework|Any CPU.Build.0 = Release|Any CPU + {08083F40-98AA-4176-BA01-817C18C35C7F}.Release_Unity|Any CPU.ActiveCfg = Release|Any CPU + {08083F40-98AA-4176-BA01-817C18C35C7F}.Release_Unity|Any CPU.Build.0 = Release|Any CPU + {08083F40-98AA-4176-BA01-817C18C35C7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08083F40-98AA-4176-BA01-817C18C35C7F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -111,6 +129,8 @@ Global {D404C973-441D-48ED-B266-C21320BA0D87} = {6E2DD21E-0854-4F4A-B925-D90E0016D03D} {490B052B-7F23-4052-AD04-613856618901} = {6EC98884-A3DC-4828-85EF-5F10F7519429} {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} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {55AC11EF-F568-4C79-A356-7ED9510145B1}