protected virtual void InitializeLoggers() { if (ConsoleManager.ConfigConsoleEnabled.Value && !ConsoleManager.ConsoleActive) { ConsoleManager.CreateConsole(); } if (ConsoleManager.ConsoleActive) { if (!Logger.Listeners.Any(x => x is ConsoleLogListener)) { Logger.Listeners.Add(new ConsoleLogListener()); } ConsoleManager.SetConsoleTitle(ConsoleTitle); } if (ConfigDiskLogging.Value) { Logger.Listeners.Add(new DiskLogListener("LogOutput.log", ConfigDiskLoggingDisplayedLevel.Value, ConfigDiskAppend.Value, ConfigDiskLoggingInstantFlushing.Value, ConfigDiskLoggingFileLimit.Value)); } if (!TraceLogSource.IsListening) { Logger.Sources.Add(TraceLogSource.CreateSource()); } if (!Logger.Sources.Any(x => x is HarmonyLogSource)) { Logger.Sources.Add(new HarmonyLogSource()); } }
/// <summary> /// Initializes BepInEx to be able to start the chainloader. /// </summary> public static void Initialize(string gameExePath, bool startConsole = true) { if (_initialized) { return; } ThreadingHelper.Initialize(); // Set vitals if (gameExePath != null) { // Checking for null allows a more advanced initialization workflow, where the Paths class has been initialized before calling Chainloader.Initialize // This is used by Preloader to use environment variables, for example Paths.SetExecutablePath(gameExePath); } // Start logging if (ConsoleWindow.ConfigConsoleEnabled.Value && startConsole) { ConsoleWindow.Attach(); Logger.Listeners.Add(new ConsoleLogListener()); } // Fix for standard output getting overwritten by UnityLogger if (ConsoleWindow.StandardOut != null) { Console.SetOut(ConsoleWindow.StandardOut); var encoding = ConsoleWindow.ConfigConsoleShiftJis.Value ? 932 : (uint)Encoding.UTF8.CodePage; ConsoleEncoding.ConsoleCodePage = encoding; Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding); } Logger.Listeners.Add(new UnityLogListener()); if (ConfigDiskLogging.Value) { Logger.Listeners.Add(new DiskLogListener("LogOutput.log", ConfigDiskConsoleDisplayedLevel.Value, ConfigDiskAppend.Value, ConfigDiskWriteUnityLog.Value)); } if (!TraceLogSource.IsListening) { Logger.Sources.Add(TraceLogSource.CreateSource()); } if (ConfigUnityLogging.Value) { Logger.Sources.Add(new UnityLogSource()); } Logger.LogMessage("Chainloader ready"); _initialized = true; }
/// <summary> /// Creates a new trace log source. /// </summary> /// <returns>New log source (or already existing one).</returns> public static ILogSource CreateSource() { if (traceListener == null) { traceListener = new TraceLogSource(); Trace.Listeners.Add(traceListener); IsListening = true; } return(traceListener.LogSource); }
/// <summary> /// Initializes BepInEx to be able to start the chainloader. /// </summary> public static void Initialize(string gameExePath, bool startConsole = true, ICollection <LogEventArgs> preloaderLogEvents = null) { if (_initialized) { return; } ThreadingHelper.Initialize(); // Set vitals if (gameExePath != null) { // Checking for null allows a more advanced initialization workflow, where the Paths class has been initialized before calling Chainloader.Initialize // This is used by Preloader to use environment variables, for example Paths.SetExecutablePath(gameExePath); } // Start logging if (ConsoleManager.ConfigConsoleEnabled.Value && startConsole) { ConsoleManager.CreateConsole(); Logger.Listeners.Add(new ConsoleLogListener()); } Logger.InitializeInternalLoggers(); if (ConfigDiskLogging.Value) { Logger.Listeners.Add(new DiskLogListener("LogOutput.log", ConfigDiskConsoleDisplayedLevel.Value, ConfigDiskAppend.Value, ConfigDiskWriteUnityLog.Value)); } if (!TraceLogSource.IsListening) { Logger.Sources.Add(TraceLogSource.CreateSource()); } ReplayPreloaderLogs(preloaderLogEvents); // Add Unity log source only after replaying to prevent duplication in console if (ConfigUnityLogging.Value) { Logger.Sources.Add(new UnityLogSource()); } Logger.Listeners.Add(new UnityLogListener()); if (Utility.CurrentOs == Platform.Linux) { Logger.LogInfo($"Detected Unity version: v{Application.unityVersion}"); } Logger.LogMessage("Chainloader ready"); _initialized = true; }
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(); }
/// <summary> /// Initializes BepInEx to be able to start the chainloader. /// </summary> public static void Initialize(string containerExePath, bool startConsole = true) { if (_initialized) { return; } //Set vitals Paths.SetExecutablePath(containerExePath); Paths.SetPluginPath(ConfigPluginsDirectory.Value); //Start logging if (startConsole) { ConsoleWindow.Attach(); ConsoleEncoding.ConsoleCodePage = (uint)Encoding.UTF8.CodePage; Console.OutputEncoding = Encoding.UTF8; Logger.Listeners.Add(new ConsoleLogListener()); } //Fix for standard output getting overwritten by UnityLogger if (ConsoleWindow.StandardOut != null) { Console.SetOut(ConsoleWindow.StandardOut); } Logger.Listeners.Add(new UnityLogListener()); Logger.Listeners.Add(new DiskLogListener()); if (!TraceLogSource.IsListening) { Logger.Sources.Add(TraceLogSource.CreateSource()); } if (ConfigUnityLogging.Value) { Logger.Sources.Add(new UnityLogSource()); } Logger.LogMessage("Chainloader ready"); _initialized = true; }
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); Logger.Sources.Add(TraceLogSource.CreateSource()); ChainloaderLogHelper.PrintLogInfo(Log); Log.LogInfo($"CLR runtime version: {Environment.Version}"); 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 }); }
public static void Run() { try { AllocateConsole(); bool bridgeInitialized = Utility.TryDo(() => { if (ConfigShimHarmony.Value) { HarmonyDetourBridge.Init(); } }, out var harmonyBridgeException); Exception runtimePatchException = null; if (bridgeInitialized) { Utility.TryDo(() => { if (ConfigApplyRuntimePatches.Value) { UnityPatches.Apply(); } }, out runtimePatchException); } Logger.Sources.Add(TraceLogSource.CreateSource()); HarmonyFixes.Apply(); PreloaderLog = new PreloaderConsoleListener(ConfigPreloaderCOutLogging.Value); Logger.Listeners.Add(PreloaderLog); string consoleTile = $"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}"; ConsoleWindow.Title = consoleTile; Logger.LogMessage(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]; Logger.LogMessage(attribute.Info); } Logger.LogInfo($"Running under Unity v{FileVersionInfo.GetVersionInfo(Paths.ExecutablePath).FileVersion}"); Logger.LogInfo($"CLR runtime version: {Environment.Version}"); Logger.LogInfo($"Supports SRE: {Utility.CLRSupportsDynamicAssemblies}"); if (harmonyBridgeException != null) { Logger.LogWarning($"Failed to enable fix for Harmony for .NET Standard API. Error message: {harmonyBridgeException.Message}"); } if (runtimePatchException != null) { Logger.LogWarning($"Failed to apply runtime patches for Mono. See more info in the output log. Error message: {runtimePatchException.Message}"); } Logger.LogMessage("Preloader started"); AssemblyPatcher.AddPatcher(new PatcherPlugin { TargetDLLs = () => new[] { ConfigEntrypointAssembly.Value }, Patcher = PatchEntrypoint, TypeName = "BepInEx.Chainloader" }); AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath); Logger.LogInfo($"{AssemblyPatcher.PatcherPlugins.Count} patcher plugin(s) loaded"); AssemblyPatcher.PatchAndLoad(Paths.ManagedPath); AssemblyPatcher.DisposePatchers(); Logger.LogMessage("Preloader finished"); Logger.Listeners.Remove(PreloaderLog); Logger.Listeners.Add(new ConsoleLogListener()); PreloaderLog.Dispose(); } catch (Exception ex) { try { Logger.LogFatal("Could not run preloader!"); Logger.LogFatal(ex); PreloaderLog?.Dispose(); if (!ConsoleWindow.IsAttached) { //if we've already attached the console, then the log will already be written to the console AllocateConsole(); Console.Write(PreloaderLog); } PreloaderLog = null; } finally { File.WriteAllText( Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"), PreloaderLog + "\r\n" + ex); PreloaderLog?.Dispose(); PreloaderLog = null; } } }
/// <summary> /// Initializes BepInEx to be able to start the chainloader. /// </summary> public static void Initialize(string gameExePath, bool startConsole = true, ICollection <LogEventArgs> preloaderLogEvents = null) { if (_initialized) { return; } ThreadingHelper.Initialize(); // Set vitals if (gameExePath != null) { // Checking for null allows a more advanced initialization workflow, where the Paths class has been initialized before calling Chainloader.Initialize // This is used by Preloader to use environment variables, for example Paths.SetExecutablePath(gameExePath); } // Start logging if (ConsoleManager.ConfigConsoleEnabled.Value && startConsole) { ConsoleManager.CreateConsole(); Logger.Listeners.Add(new ConsoleLogListener()); } // Fix for standard output getting overwritten by UnityLogger if (ConsoleManager.ConsoleActive) { ConsoleManager.SetConsoleStreams(); ConsoleManager.SetConsoleEncoding(); } Logger.InitializeInternalLoggers(); Logger.Listeners.Add(new UnityLogListener()); if (ConfigDiskLogging.Value) { Logger.Listeners.Add(new DiskLogListener("LogOutput.log", ConfigDiskConsoleDisplayedLevel.Value, ConfigDiskAppend.Value, ConfigDiskWriteUnityLog.Value)); } if (!TraceLogSource.IsListening) { Logger.Sources.Add(TraceLogSource.CreateSource()); } if (ConfigUnityLogging.Value) { Logger.Sources.Add(new UnityLogSource()); } // Temporarily disable the console log listener as we replay the preloader logs var logListener = Logger.Listeners.FirstOrDefault(logger => logger is ConsoleLogListener); if (logListener != null) { Logger.Listeners.Remove(logListener); } // Write preloader log events if there are any, including the original log source name if (preloaderLogEvents != null) { var preloaderLogSource = Logger.CreateLogSource("Preloader"); foreach (var preloaderLogEvent in preloaderLogEvents) { Logger.InternalLogEvent(preloaderLogSource, preloaderLogEvent); } Logger.Sources.Remove(preloaderLogSource); } if (logListener != null) { Logger.Listeners.Add(logListener); } if (Utility.CurrentOs == Platform.Linux) { Logger.LogInfo($"Detected Unity version: v{Application.unityVersion}"); } Logger.LogMessage("Chainloader ready"); _initialized = true; }
/// <summary> /// Initializes BepInEx to be able to start the chainloader. /// </summary> public static void Initialize(string gameExePath, bool startConsole = true, ICollection <LogEventArgs> preloaderLogEvents = null) { if (_initialized) { return; } ThreadingHelper.Initialize(); // Set vitals if (gameExePath != null) { // Checking for null allows a more advanced initialization workflow, where the Paths class has been initialized before calling Chainloader.Initialize // This is used by Preloader to use environment variables, for example Paths.SetExecutablePath(gameExePath); } // Start logging if (ConsoleWindow.ConfigConsoleEnabled.Value && startConsole) { ConsoleWindow.Attach(); Logger.Listeners.Add(new ConsoleLogListener()); } // Fix for standard output getting overwritten by UnityLogger if (ConsoleWindow.StandardOut != null) { Console.SetOut(ConsoleWindow.StandardOut); var encoding = ConsoleWindow.ConfigConsoleShiftJis.Value ? 932 : (uint)Encoding.UTF8.CodePage; ConsoleEncoding.ConsoleCodePage = encoding; Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding); } Logger.Listeners.Add(new UnityLogListener()); if (ConfigDiskLogging.Value) { Logger.Listeners.Add(new DiskLogListener("LogOutput.log", ConfigDiskConsoleDisplayedLevel.Value, ConfigDiskAppend.Value, ConfigDiskWriteUnityLog.Value)); } if (!TraceLogSource.IsListening) { Logger.Sources.Add(TraceLogSource.CreateSource()); } if (ConfigUnityLogging.Value) { Logger.Sources.Add(new UnityLogSource()); } // Temporarily disable the console log listener as we replay the preloader logs var logListener = Logger.Listeners.FirstOrDefault(logger => logger is ConsoleLogListener); if (logListener != null) { Logger.Listeners.Remove(logListener); } // Write preloader log events if there are any, including the original log source name if (preloaderLogEvents != null) { var preloaderLogSource = Logger.CreateLogSource("Preloader"); foreach (var preloaderLogEvent in preloaderLogEvents) { preloaderLogSource.Log(preloaderLogEvent.Level, $"[{preloaderLogEvent.Source.SourceName,10}] {preloaderLogEvent.Data}"); } Logger.Sources.Remove(preloaderLogSource); } if (logListener != null) { Logger.Listeners.Add(logListener); } Logger.LogMessage("Chainloader ready"); _initialized = true; }
public static void Run() { try { AllocateConsole(); UnityPatches.Apply(); Logger.Sources.Add(TraceLogSource.CreateSource()); PreloaderLog = new PreloaderConsoleListener(Utility.SafeParseBool(Config.GetEntry("preloader-logconsole", "false", "BepInEx"))); Logger.Listeners.Add(PreloaderLog); string consoleTile = $"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}"; ConsoleWindow.Title = consoleTile; Logger.LogMessage(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]; Logger.LogMessage(attribute.Info); } #if UNITY_2018 Logger.LogMessage("Compiled in Unity v2018 mode"); #else Logger.LogMessage("Compiled in Legacy Unity mode"); #endif Logger.LogInfo($"Running under Unity v{Process.GetCurrentProcess().MainModule.FileVersionInfo.FileVersion}"); Logger.LogMessage("Preloader started"); string entrypointAssembly = Config.GetEntry("entrypoint-assembly", "UnityEngine.dll", "Preloader"); AssemblyPatcher.AddPatcher(new PatcherPlugin { TargetDLLs = new[] { entrypointAssembly }, Patcher = PatchEntrypoint }); AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath, GetPatcherMethods); Logger.LogInfo($"{AssemblyPatcher.PatcherPlugins.Count} patcher plugin(s) loaded"); AssemblyPatcher.PatchAndLoad(Paths.ManagedPath); AssemblyPatcher.DisposePatchers(); Logger.LogMessage("Preloader finished"); Logger.Listeners.Remove(PreloaderLog); Logger.Listeners.Add(new ConsoleLogListener()); PreloaderLog.Dispose(); } catch (Exception ex) { try { Logger.LogFatal("Could not run preloader!"); Logger.LogFatal(ex); PreloaderLog?.Dispose(); if (!ConsoleWindow.IsAttached) { //if we've already attached the console, then the log will already be written to the console AllocateConsole(); Console.Write(PreloaderLog); } PreloaderLog = null; } finally { File.WriteAllText( Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"), PreloaderLog + "\r\n" + ex); PreloaderLog?.Dispose(); PreloaderLog = null; } } }
public static void Run() { try { ConsoleManager.Initialize(false); AllocateConsole(); Utility.TryDo(() => { if (ConfigApplyRuntimePatches.Value) { UnityPatches.Apply(); } }, out var runtimePatchException); Logger.InitializeInternalLoggers(); Logger.Sources.Add(TraceLogSource.CreateSource()); PreloaderLog = new PreloaderConsoleListener(ConfigPreloaderCOutLogging.Value); Logger.Listeners.Add(PreloaderLog); string consoleTile = $"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().ProcessName)}"; if (ConsoleManager.ConsoleActive) { ConsoleManager.SetConsoleTitle(consoleTile); } Logger.LogMessage(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]; Logger.LogMessage(attribute.Info); } Logger.LogInfo($"Running under Unity v{GetUnityVersion()}"); Logger.LogInfo($"CLR runtime version: {Environment.Version}"); Logger.LogInfo($"Supports SRE: {Utility.CLRSupportsDynamicAssemblies}"); if (runtimePatchException != null) { Logger.LogWarning($"Failed to apply runtime patches for Mono. See more info in the output log. Error message: {runtimePatchException.Message}"); } Logger.LogMessage("Preloader started"); AssemblyPatcher.AddPatcher(new PatcherPlugin { TargetDLLs = () => new[] { ConfigEntrypointAssembly.Value }, Patcher = PatchEntrypoint, TypeName = "BepInEx.Chainloader" }); AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath); Logger.LogInfo($"{AssemblyPatcher.PatcherPlugins.Count} patcher plugin(s) loaded"); AssemblyPatcher.PatchAndLoad(Paths.ManagedPath); AssemblyPatcher.DisposePatchers(); Logger.LogMessage("Preloader finished"); Logger.Listeners.Remove(PreloaderLog); Logger.Listeners.Add(new ConsoleLogListener()); PreloaderLog.Dispose(); } catch (Exception ex) { try { Logger.LogFatal("Could not run preloader!"); Logger.LogFatal(ex); if (!ConsoleManager.ConsoleActive) { //if we've already attached the console, then the log will already be written to the console AllocateConsole(); Console.Write(PreloaderLog); } } catch { } string log = string.Empty; try { // We could use platform-dependent newlines, however the developers use Windows so this will be easier to read :) log = string.Join("\r\n", PreloaderConsoleListener.LogEvents.Select(x => x.ToString()).ToArray()); log += "\r\n"; PreloaderLog?.Dispose(); PreloaderLog = null; } catch { } File.WriteAllText( Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"), log + ex); } }
public static void Run() { try { InitializeHarmony(); ConsoleManager.Initialize(false); AllocateConsole(); Utility.TryDo(() => { if (ConfigApplyRuntimePatches.Value) { UnityPatches.Apply(); } }, out var runtimePatchException); Logger.Sources.Add(new HarmonyLogSource()); Logger.Sources.Add(TraceLogSource.CreateSource()); Logger.Listeners.Add(new ConsoleLogListener()); PreloaderLog = new PreloaderConsoleListener(); Logger.Listeners.Add(PreloaderLog); ChainloaderLogHelper.PrintLogInfo(Log); Log.LogInfo($"Running under Unity v{GetUnityVersion()}"); Log.LogInfo($"CLR runtime version: {Environment.Version}"); Log.LogInfo($"Supports SRE: {Utility.CLRSupportsDynamicAssemblies}"); Log.LogDebug($"Game executable path: {Paths.ExecutablePath}"); Log.LogDebug($"Unity Managed directory: {Paths.ManagedPath}"); Log.LogDebug($"BepInEx root path: {Paths.BepInExRootPath}"); if (runtimePatchException != null) { Log.LogWarning($"Failed to apply runtime patches for Mono. See more info in the output log. Error message: {runtimePatchException.Message}"); } Log.LogMessage("Preloader started"); TypeLoader.SearchDirectories.UnionWith(Paths.DllSearchPaths); using (var assemblyPatcher = new AssemblyPatcher()) { assemblyPatcher.PatcherPlugins.Add(new PatcherPlugin { TargetDLLs = () => new[] { ConfigEntrypointAssembly.Value }, Patcher = PatchEntrypoint, TypeName = "BepInEx.Chainloader" }); assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath); Log.LogInfo($"{assemblyPatcher.PatcherPlugins.Count} patcher plugin{(assemblyPatcher.PatcherPlugins.Count == 1 ? "" : "s")} loaded"); assemblyPatcher.LoadAssemblyDirectories(Paths.DllSearchPaths); Log.LogInfo($"{assemblyPatcher.PatcherPlugins.Count} assemblies discovered"); assemblyPatcher.PatchAndLoad(); } Log.LogMessage("Preloader finished"); Logger.Listeners.Remove(PreloaderLog); PreloaderLog.Dispose(); } catch (Exception ex) { try { Log.LogFatal("Could not run preloader!"); Log.LogFatal(ex); if (!ConsoleManager.ConsoleActive) { //if we've already attached the console, then the log will already be written to the console AllocateConsole(); Console.Write(PreloaderLog); } } catch { } var log = string.Empty; try { // We could use platform-dependent newlines, however the developers use Windows so this will be easier to read :) log = string.Join("\r\n", PreloaderConsoleListener.LogEvents.Select(x => x.ToString()).ToArray()); log += "\r\n"; PreloaderLog?.Dispose(); PreloaderLog = null; } catch { } File.WriteAllText( Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"), log + ex); } }
public static void Start(string[] args) { var preloaderListener = new PreloaderConsoleListener(); Logger.Listeners.Add(preloaderListener); if (string.IsNullOrEmpty(ConfigEntrypointExecutable.Value)) { Log.Log(LogLevel.Fatal, $"Entry executable was not set. Please set this in your config before launching the application"); Program.ReadExit(); return; } var executablePath = Path.GetFullPath(ConfigEntrypointExecutable.Value); if (!File.Exists(executablePath)) { Log.Log(LogLevel.Fatal, $"Unable to locate executable: {ConfigEntrypointExecutable.Value}"); Program.ReadExit(); return; } Paths.SetExecutablePath(executablePath); Program.ResolveDirectories.Add(Paths.GameRootPath); foreach (var searchDir in Program.ResolveDirectories) { TypeLoader.SearchDirectories.Add(searchDir); } if (PlatformHelper.Is(Platform.Windows)) { AddDllDirectory(Paths.GameRootPath); SetDllDirectory(Paths.GameRootPath); } Logger.Sources.Add(TraceLogSource.CreateSource()); ChainloaderLogHelper.PrintLogInfo(Log); Log.Log(LogLevel.Info, $"CLR runtime version: {Environment.Version}"); AssemblyBuildInfo executableInfo, launcherInfo; using (var executableAssembly = AssemblyDefinition.ReadAssembly(executablePath)) executableInfo = AssemblyBuildInfo.DetermineInfo(executableAssembly); using (var launcherAssembly = AssemblyDefinition.ReadAssembly(typeof(NetPreloader).Assembly.Location)) launcherInfo = AssemblyBuildInfo.DetermineInfo(launcherAssembly); // we don't particularly care about AnyCPU here since the fallback bitness is almost never the case if (executableInfo.Is64Bit != launcherInfo.Is64Bit) { Log.LogError($"Game executable is {(executableInfo.Is64Bit ? "64" : "32")}-bit while BepInEx has been compiled as {(launcherInfo.Is64Bit ? "64" : "32")}-bit. Expect crashes"); } if (executableInfo.NetFrameworkVersion != launcherInfo.NetFrameworkVersion || executableInfo.AssemblyFrameworkType != launcherInfo.AssemblyFrameworkType) { Log.LogWarning($"Game executable has been compiled as {executableInfo}, while BepInEx has been compiled as {launcherInfo}. There may be issues within the game caused by this"); } Log.LogInfo($"Game executable build architecture: {executableInfo}"); Log.LogInfo($"BepInEx launcher build architecture: {launcherInfo}"); Log.Log(LogLevel.Message, "Preloader started"); Assembly entrypointAssembly; 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(); var assemblyName = AssemblyName.GetAssemblyName(executablePath); entrypointAssembly = assemblyPatcher.PatcherContext.LoadedAssemblies.Values.FirstOrDefault(x => x.FullName == assemblyName.FullName); foreach (var loadedAssembly in assemblyPatcher.PatcherContext.LoadedAssemblies) { // TODO: Need full paths for loaded assemblies var assemblyPath = Path.Combine(Paths.GameRootPath, loadedAssembly.Key); Log.LogDebug($"Registering '{assemblyPath}' as a loaded assembly"); AssemblyFixes.AssemblyLocations[loadedAssembly.Value.FullName] = assemblyPath; } 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"); Logger.Listeners.Remove(preloaderListener); var chainloader = new NetChainloader(); chainloader.Initialize(); chainloader.Execute(); AssemblyFixes.Execute(entrypointAssembly); try { var argList = new List <object>(); var paramTypes = entrypointAssembly.EntryPoint.GetParameters(); if (paramTypes.Length == 1 && paramTypes[0].ParameterType == typeof(string[])) { argList.Add(args); } else if (paramTypes.Length == 1 && paramTypes[0].ParameterType == typeof(string)) { argList.Add(string.Join(" ", args)); } else if (paramTypes.Length != 0) { // Only other entrypoint signatures I can think of that .NET supports is Task / Task<int> // async entrypoints. That's a can of worms for another time though Log.LogFatal($"Could not figure out how to handle entrypoint method with this signature: {entrypointAssembly.EntryPoint.FullDescription()}"); return; } entrypointAssembly.EntryPoint.Invoke(null, argList.ToArray()); } catch (Exception ex) { Log.LogFatal($"Unhandled exception: {ex}"); } }