using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using BepInEx.NetLauncher.RuntimeFixes; using BepInEx.Preloader.Core; using BepInEx.Preloader.Core.RuntimeFixes; using MonoMod.RuntimeDetour; namespace BepInEx.NetLauncher { public static class NetPreloader { private static readonly ManualLogSource Log = PreloaderLogger.Log; public static void Start(string[] args) { if (ConfigEntrypointExecutable.Value == null) { Log.LogFatal($"Entry executable was not set. Please set this in your config before launching the application"); Program.ReadExit(); return; } string executablePath = Path.GetFullPath(ConfigEntrypointExecutable.Value); if (!File.Exists(executablePath)) { Log.LogFatal($"Unable to locate executable: {ConfigEntrypointExecutable.Value}"); Program.ReadExit(); return; } Paths.SetExecutablePath(executablePath); Program.ResolveDirectories.Add(Paths.GameRootPath); TypeLoader.SearchDirectories.Add(Paths.GameRootPath); bool bridgeInitialized = Utility.TryDo(() => { if (ConfigShimHarmony.Value) HarmonyDetourBridge.Init(); }, out var harmonyBridgeException); Logger.Sources.Add(TraceLogSource.CreateSource()); HarmonyFixes.Apply(); string consoleTile = $"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}"; Log.LogMessage(consoleTile); if (ConsoleManager.ConsoleActive) ConsoleManager.SetConsoleTitle(consoleTile); //See BuildInfoAttribute for more information about this section. object[] attributes = typeof(BuildInfoAttribute).Assembly.GetCustomAttributes(typeof(BuildInfoAttribute), false); if (attributes.Length > 0) { var attribute = (BuildInfoAttribute)attributes[0]; Log.LogMessage(attribute.Info); } Log.LogInfo($"CLR runtime version: {Environment.Version}"); if (harmonyBridgeException != null) Log.LogWarning($"Failed to enable fix for Harmony for .NET Standard API. Error message: {harmonyBridgeException.Message}"); Log.LogMessage("Preloader started"); Assembly entrypointAssembly; using (var assemblyPatcher = new AssemblyPatcher()) { assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath); Log.LogInfo($"{assemblyPatcher.PatcherPlugins.Count} patcher plugin(s) loaded"); assemblyPatcher.LoadAssemblyDirectory(Paths.GameRootPath, "dll", "exe"); Log.LogInfo($"{assemblyPatcher.AssembliesToPatch.Count} assemblies discovered"); assemblyPatcher.PatchAndLoad(); var assemblyName = AssemblyName.GetAssemblyName(executablePath); entrypointAssembly = assemblyPatcher.LoadedAssemblies.Values.FirstOrDefault(x => x.FullName == assemblyName.FullName); if (entrypointAssembly != null) { Log.LogDebug("Found patched entrypoint assembly! Using it"); } else { Log.LogDebug("Using entrypoint assembly from disk"); entrypointAssembly = Assembly.LoadFrom(executablePath); } } Log.LogMessage("Preloader finished"); var chainloader = new NetChainloader(); chainloader.Initialize(); chainloader.Execute(); AssemblyFix.Execute(entrypointAssembly); entrypointAssembly.EntryPoint.Invoke(null, new [] { args }); } #region Config private static readonly ConfigEntry ConfigEntrypointExecutable = ConfigFile.CoreConfig.Bind( "Preloader.Entrypoint", "Assembly", null, "The local filename of the .NET executable to target."); private static readonly ConfigEntry ConfigShimHarmony = ConfigFile.CoreConfig.Bind( "Preloader", "ShimHarmonySupport", !Utility.CLRSupportsDynamicAssemblies, "If enabled, basic Harmony functionality is patched to use MonoMod's RuntimeDetour instead.\nTry using this if Harmony does not work in a game."); private static readonly ConfigEntry ConfigPreloaderCOutLogging = ConfigFile.CoreConfig.Bind( "Logging", "PreloaderConsoleOutRedirection", true, "Redirects text from Console.Out during preloader patch loading to the BepInEx logging system."); #endregion } }