示例#1
0
        /// <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.ExecutablePath = containerExePath;

            //Start logging

            if (startConsole)
            {
                ConsoleWindow.Attach();

                ConsoleEncoding.ConsoleCodePage = (uint)Encoding.UTF8.CodePage;
                Console.OutputEncoding          = Encoding.UTF8;
            }

            UnityLogWriter unityLogWriter = new UnityLogWriter();

            if (Preloader.PreloaderLog != null)
            {
                unityLogWriter.WriteToLog($"{Preloader.PreloaderLog}\r\n");
            }

            Logger.SetLogger(unityLogWriter);


            _initialized = true;
        }
示例#2
0
        /// <summary>
        ///     Allocates a console window for use by BepInEx safely.
        /// </summary>
        public static void AllocateConsole()
        {
            if (!ConsoleWindow.ConfigConsoleEnabled.Value)
            {
                return;
            }

            try
            {
                ConsoleWindow.Attach();

                var encoding = (uint)Encoding.UTF8.CodePage;

                if (ConsoleWindow.ConfigConsoleShiftJis.Value)
                {
                    encoding = 932;
                }

                ConsoleEncoding.ConsoleCodePage = encoding;
                Console.OutputEncoding          = ConsoleEncoding.GetEncoding(encoding);
            }
            catch (Exception ex)
            {
                Logger.LogError("Failed to allocate console!");
                Logger.LogError(ex);
            }
        }
示例#3
0
        /// <summary>
        ///     Allocates a console window for use by BepInEx safely.
        /// </summary>
        public static void AllocateConsole()
        {
            bool console  = Utility.SafeParseBool(Config.GetEntry("console", "false", "BepInEx"));
            bool shiftjis = Utility.SafeParseBool(Config.GetEntry("console-shiftjis", "false", "BepInEx"));

            if (!console)
            {
                return;
            }

            try
            {
                ConsoleWindow.Attach();

                var encoding = (uint)Encoding.UTF8.CodePage;

                if (shiftjis)
                {
                    encoding = 932;
                }

                ConsoleEncoding.ConsoleCodePage = encoding;
                Console.OutputEncoding          = ConsoleEncoding.GetEncoding(encoding);
            }
            catch (Exception ex)
            {
                Logger.LogError("Failed to allocate console!");
                Logger.LogError(ex);
            }
        }
示例#4
0
        public void CreateConsole(uint codepage)
        {
            // On some Unity mono builds the SafeFileHandle overload for FileStream is missing
            // so we use the older but always included one instead
#pragma warning disable 618
            ConsoleWindow.Attach();

            // Make sure of ConsoleEncoding helper class because on some Monos
            // Encoding.GetEncoding throws NotImplementedException on most codepages
            // NOTE: We don't set Console.OutputEncoding because it resets any existing Console.Out writers
            ConsoleEncoding.ConsoleCodePage = codepage;

            // If stdout exists, write to it, otherwise make it the same as console out
            // Not sure if this is needed? Does the original Console.Out still work?
            var stdout            = ConsoleWindow.OriginalStdoutHandle != IntPtr.Zero ? ConsoleWindow.OriginalStdoutHandle : ConsoleWindow.ConsoleOutHandle;
            var originalOutStream = new FileStream(stdout, FileAccess.Write);
            StandardOut = new StreamWriter(originalOutStream, new UTF8Encoding(false))
            {
                AutoFlush = true
            };

            var consoleOutStream = new FileStream(ConsoleWindow.ConsoleOutHandle, FileAccess.Write);
            // Can't use Console.OutputEncoding because it can be null (i.e. not preference by user)
            ConsoleOut = new StreamWriter(consoleOutStream, ConsoleEncoding.OutputEncoding)
            {
                AutoFlush = true
            };
            ConsoleActive = true;
#pragma warning restore 618
        }
示例#5
0
        /// <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;
        }
示例#6
0
        /// <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;
        }
示例#7
0
        public void CreateConsole(uint codepage)
        {
            ConsoleWindow.Attach();

            // Make sure of ConsoleEncoding helper class because on some Monos
            // Encoding.GetEncoding throws NotImplementedException on most codepages
            // NOTE: We don't set Console.OutputEncoding because it resets any existing Console.Out writers
            ConsoleEncoding.ConsoleCodePage = codepage;

            // If stdout exists, write to it, otherwise make it the same as console out
            // Not sure if this is needed? Does the original Console.Out still work?
            var stdout = GetOutHandle();

            if (stdout == IntPtr.Zero)
            {
                StandardOut = TextWriter.Null;
                ConsoleOut  = TextWriter.Null;
                return;
            }

            var originalOutStream = OpenFileStream(stdout);

            StandardOut = new StreamWriter(originalOutStream, new UTF8Encoding(false))
            {
                AutoFlush = true
            };

            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)
            {
                AutoFlush = true
            };
            ConsoleActive = true;
        }
示例#8
0
        public void CreateConsole()
        {
            // On some Unity mono builds the SafeFileHandle overload for FileStream is missing
            // so we use the older but always included one instead
#pragma warning disable 618
            ConsoleWindow.Attach();

            // If stdout exists, write to it, otherwise make it the same as console out
            // Not sure if this is needed? Does the original Console.Out still work?
            var stdout            = ConsoleWindow.OriginalStdoutHandle != IntPtr.Zero ? ConsoleWindow.OriginalStdoutHandle : ConsoleWindow.ConsoleOutHandle;
            var originalOutStream = new FileStream(stdout, FileAccess.Write);
            StandardOut = new StreamWriter(originalOutStream, new UTF8Encoding(false))
            {
                AutoFlush = true
            };

            var consoleOutStream = new FileStream(ConsoleWindow.ConsoleOutHandle, FileAccess.Write);
            ConsoleOut = new StreamWriter(consoleOutStream, Console.OutputEncoding)
            {
                AutoFlush = true
            };
            ConsoleActive = true;
#pragma warning restore 618
        }
示例#9
0
        /// <summary>
        /// The entrypoint for the BepInEx plugin system.
        /// </summary>
        public static void Start()
        {
            if (_loaded)
            {
                return;
            }

            if (!_initialized)
            {
                throw new InvalidOperationException("BepInEx has not been initialized. Please call Chainloader.Initialize prior to starting the chainloader instance.");
            }

            if (!Directory.Exists(Paths.PluginPath))
            {
                Directory.CreateDirectory(Paths.PluginPath);
            }

            if (!Directory.Exists(Paths.PatcherPluginPath))
            {
                Directory.CreateDirectory(Paths.PatcherPluginPath);
            }

            try
            {
                var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static);
                if (productNameProp != null)
                {
                    ConsoleWindow.Title =
                        $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {productNameProp.GetValue(null, null)}";
                }

                Logger.LogMessage("Chainloader started");

                ManagerObject = new GameObject("BepInEx_Manager");

                UnityEngine.Object.DontDestroyOnLoad(ManagerObject);


                string currentProcess = Process.GetCurrentProcess().ProcessName.ToLower();

                var globalPluginTypes = TypeLoader.LoadTypes <BaseUnityPlugin>(Paths.PluginPath).ToList();

                var selectedPluginTypes = globalPluginTypes
                                          .Where(plugin =>
                {
                    //Ensure metadata exists
                    var metadata = MetadataHelper.GetMetadata(plugin);

                    if (metadata == null)
                    {
                        Logger.LogWarning($"Skipping over type [{plugin.Name}] as no metadata attribute is specified");
                        return(false);
                    }

                    //Perform a filter for currently running process
                    var filters = MetadataHelper.GetAttributes <BepInProcess>(plugin);

                    if (filters.Length == 0)                                                                       //no filters means it loads everywhere
                    {
                        return(true);
                    }

                    var result = filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess);

                    if (!result)
                    {
                        Logger.LogInfo($"Skipping over plugin [{metadata.GUID}] due to process filter");
                    }

                    return(result);
                })
                                          .ToList();

                Logger.LogInfo($"{selectedPluginTypes.Count} / {globalPluginTypes.Count} plugins to load");

                var dependencyDict = new Dictionary <string, IEnumerable <string> >();
                var pluginsByGUID  = new Dictionary <string, Type>();

                foreach (Type t in selectedPluginTypes)
                {
                    var dependencies = MetadataHelper.GetDependencies(t, selectedPluginTypes);
                    var metadata     = MetadataHelper.GetMetadata(t);

                    if (metadata.GUID == null)
                    {
                        Logger.LogWarning($"Skipping [{metadata.Name}] because it does not have a valid GUID.");
                        continue;
                    }

                    if (dependencyDict.ContainsKey(metadata.GUID))
                    {
                        Logger.LogWarning($"Skipping [{metadata.Name}] because its GUID ({metadata.GUID}) is already used by another plugin.");
                        continue;
                    }

                    dependencyDict[metadata.GUID] = dependencies.Select(d => d.DependencyGUID);
                    pluginsByGUID[metadata.GUID]  = t;
                }

                var emptyDependencies = new string[0];

                // Sort plugins by their dependencies.
                // Give missing dependencies no dependencies of its own, which will cause missing plugins to be first in the resulting list.
                var sortedPlugins = Utility.TopologicalSort(dependencyDict.Keys, x => dependencyDict.TryGetValue(x, out var deps) ? deps : emptyDependencies).ToList();

                var invalidPlugins   = new HashSet <string>();
                var processedPlugins = new HashSet <string>();

                foreach (var pluginGUID in sortedPlugins)
                {
                    // If the plugin is missing, don't process it
                    if (!pluginsByGUID.TryGetValue(pluginGUID, out var pluginType))
                    {
                        continue;
                    }

                    var metadata               = MetadataHelper.GetMetadata(pluginType);
                    var dependencies           = MetadataHelper.GetDependencies(pluginType, selectedPluginTypes);
                    var dependsOnInvalidPlugin = false;
                    var missingDependencies    = new List <string>();
                    foreach (var dependency in dependencies)
                    {
                        // If the depenency wasn't already processed, it's missing altogether
                        if (!processedPlugins.Contains(dependency.DependencyGUID))
                        {
                            // If the dependency is hard, collect it into a list to show
                            if ((dependency.Flags & BepInDependency.DependencyFlags.HardDependency) != 0)
                            {
                                missingDependencies.Add(dependency.DependencyGUID);
                            }
                            continue;
                        }

                        // If the dependency is invalid (e.g. has missing depedencies), report that to the user
                        if (invalidPlugins.Contains(dependency.DependencyGUID))
                        {
                            dependsOnInvalidPlugin = true;
                            break;
                        }
                    }

                    processedPlugins.Add(pluginGUID);

                    if (dependsOnInvalidPlugin)
                    {
                        Logger.LogWarning($"Skipping [{metadata.Name}] because it has a dependency that was not loaded. See above errors for details.");
                        continue;
                    }

                    if (missingDependencies.Count != 0)
                    {
                        Logger.LogError($@"Missing the following dependencies for [{metadata.Name}]: {"\r\n"}{
							string.Join("\r\n", missingDependencies.Select(s => $"- {s}").ToArray())
							}{"\r\n"}Loading will be skipped; expect further errors and unstabilities."                            );

                        invalidPlugins.Add(pluginGUID);
                        continue;
                    }

                    try
                    {
                        Logger.LogInfo($"Loading [{metadata.Name} {metadata.Version}]");
                        Plugins.Add((BaseUnityPlugin)ManagerObject.AddComponent(pluginType));
                    }
                    catch (Exception ex)
                    {
                        invalidPlugins.Add(pluginGUID);
                        Logger.LogError($"Error loading [{metadata.Name}] : {ex.Message}");
                        Logger.LogDebug(ex);
                    }
                }
            }
            catch (Exception ex)
            {
                ConsoleWindow.Attach();

                Console.WriteLine("Error occurred starting the game");
                Console.WriteLine(ex.ToString());
            }

            Logger.LogMessage("Chainloader startup complete");

            _loaded = true;
        }
示例#10
0
        /// <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;
        }
示例#11
0
        /// <summary>
        /// The entrypoint for the BepInEx plugin system.
        /// </summary>
        public static void Start()
        {
            if (_loaded)
            {
                return;
            }

            if (!_initialized)
            {
                throw new InvalidOperationException("BepInEx has not been initialized. Please call Chainloader.Initialize prior to starting the chainloader instance.");
            }

            if (!Directory.Exists(Paths.PluginPath))
            {
                Directory.CreateDirectory(Paths.PluginPath);
            }

            try
            {
                if (bool.Parse(Config.GetEntry("chainloader-log-unity-messages", "false", "BepInEx")))
                {
                    UnityLogWriter.ListenUnityLogs();
                }

                var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static);
                if (productNameProp != null)
                {
                    ConsoleWindow.Title =
                        $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {productNameProp.GetValue(null, null)}";
                }

                Logger.Log(LogLevel.Message, "Chainloader started");

                UnityEngine.Object.DontDestroyOnLoad(ManagerObject);


                string currentProcess = Process.GetCurrentProcess().ProcessName.ToLower();

                var pluginTypes = TypeLoader.LoadTypes <BaseUnityPlugin>(Paths.PluginPath)
                                  .Where(plugin =>
                {
                    //Perform a filter for currently running process
                    var filters = MetadataHelper.GetAttributes <BepInProcess>(plugin);

                    if (!filters.Any())
                    {
                        return(true);
                    }

                    return(filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess));
                })
                                  .ToList();

                Logger.Log(LogLevel.Info, $"{pluginTypes.Count} plugins selected");

                Dictionary <Type, IEnumerable <Type> > dependencyDict = new Dictionary <Type, IEnumerable <Type> >();


                foreach (Type t in pluginTypes)
                {
                    try
                    {
                        IEnumerable <Type> dependencies = MetadataHelper.GetDependencies(t, pluginTypes);

                        dependencyDict[t] = dependencies;
                    }
                    catch (MissingDependencyException)
                    {
                        var metadata = MetadataHelper.GetMetadata(t);

                        Logger.Log(LogLevel.Info, $"Cannot load [{metadata.Name}] due to missing dependencies.");
                    }
                }

                pluginTypes = Utility.TopologicalSort(dependencyDict.Keys, x => dependencyDict[x]).ToList();

                foreach (Type t in pluginTypes)
                {
                    try
                    {
                        var metadata = MetadataHelper.GetMetadata(t);

                        var plugin = (BaseUnityPlugin)ManagerObject.AddComponent(t);

                        Plugins.Add(plugin);
                        Logger.Log(LogLevel.Info, $"Loaded [{metadata.Name} {metadata.Version}]");
                    }
                    catch (Exception ex)
                    {
                        Logger.Log(LogLevel.Info, $"Error loading [{t.Name}] : {ex.Message}");
                    }
                }
            }
            catch (Exception ex)
            {
                ConsoleWindow.Attach();

                Console.WriteLine("Error occurred starting the game");
                Console.WriteLine(ex.ToString());
            }

            _loaded = true;
        }
示例#12
0
        /// <summary>
        /// The entrypoint for the BepInEx plugin system.
        /// </summary>
        public static void Start()
        {
            if (_loaded)
            {
                return;
            }

            if (!_initialized)
            {
                throw new InvalidOperationException("BepInEx has not been initialized. Please call Chainloader.Initialize prior to starting the chainloader instance.");
            }

            if (!Directory.Exists(Paths.PluginPath))
            {
                Directory.CreateDirectory(Paths.PluginPath);
            }

            if (!Directory.Exists(Paths.PatcherPluginPath))
            {
                Directory.CreateDirectory(Paths.PatcherPluginPath);
            }

            try
            {
                var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static);
                if (productNameProp != null)
                {
                    ConsoleWindow.Title = $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {productNameProp.GetValue(null, null)}";
                }

                Logger.LogMessage("Chainloader started");

                ManagerObject = new GameObject("BepInEx_Manager");

                UnityEngine.Object.DontDestroyOnLoad(ManagerObject);

                var pluginsToLoad = TypeLoader.FindPluginTypes(Paths.PluginPath, ToPluginInfo, HasBepinPlugins, "chainloader");
                foreach (var keyValuePair in pluginsToLoad)
                {
                    foreach (var pluginInfo in keyValuePair.Value)
                    {
                        pluginInfo.Location = keyValuePair.Key;
                    }
                }
                var pluginInfos      = pluginsToLoad.SelectMany(p => p.Value).ToList();
                var loadedAssemblies = new Dictionary <string, Assembly>();

                Logger.LogInfo($"{pluginInfos.Count} plugins to load");

                var dependencyDict = new Dictionary <string, IEnumerable <string> >();
                var pluginsByGUID  = new Dictionary <string, PluginInfo>();

                foreach (var pluginInfo in pluginInfos)
                {
                    // Perform checks that will prevent loading plugins in this run
                    var  filters            = pluginInfo.Processes.ToList();
                    bool invalidProcessName = filters.Count != 0 && filters.All(x => !string.Equals(x.ProcessName.Replace(".exe", ""), Paths.ProcessName, StringComparison.InvariantCultureIgnoreCase));

                    if (invalidProcessName)
                    {
                        Logger.LogWarning($"Skipping over plugin [{pluginInfo.Metadata.GUID}] due to process filter");
                        continue;
                    }

                    if (dependencyDict.ContainsKey(pluginInfo.Metadata.GUID))
                    {
                        Logger.LogWarning($"Skipping [{pluginInfo.Metadata.Name}] because its GUID ({pluginInfo.Metadata.GUID}) is already used by another plugin.");
                        continue;
                    }

                    dependencyDict[pluginInfo.Metadata.GUID] = pluginInfo.Dependencies.Select(d => d.DependencyGUID).Concat(pluginInfo.Incompatibilities.Select(i => i.IncompatibilityGUID));
                    pluginsByGUID[pluginInfo.Metadata.GUID]  = pluginInfo;
                }

                var emptyDependencies = new string[0];

                // Sort plugins by their dependencies.
                // Give missing dependencies no dependencies of its own, which will cause missing plugins to be first in the resulting list.
                var sortedPlugins = Utility.TopologicalSort(dependencyDict.Keys, x => dependencyDict.TryGetValue(x, out var deps) ? deps : emptyDependencies).ToList();

                var invalidPlugins   = new HashSet <string>();
                var processedPlugins = new Dictionary <string, Version>();

                foreach (var pluginGUID in sortedPlugins)
                {
                    // If the plugin is missing, don't process it
                    if (!pluginsByGUID.TryGetValue(pluginGUID, out var pluginInfo))
                    {
                        continue;
                    }

                    var dependsOnInvalidPlugin = false;
                    var missingDependencies    = new List <BepInDependency>();
                    foreach (var dependency in pluginInfo.Dependencies)
                    {
                        // If the depenency wasn't already processed, it's missing altogether
                        bool depenencyExists = processedPlugins.TryGetValue(dependency.DependencyGUID, out var pluginVersion);
                        if (!depenencyExists || pluginVersion < dependency.MinimumVersion)
                        {
                            // If the dependency is hard, collect it into a list to show
                            if ((dependency.Flags & BepInDependency.DependencyFlags.HardDependency) != 0)
                            {
                                missingDependencies.Add(dependency);
                            }
                            continue;
                        }

                        // If the dependency is invalid (e.g. has missing depedencies), report that to the user
                        if (invalidPlugins.Contains(dependency.DependencyGUID))
                        {
                            dependsOnInvalidPlugin = true;
                            break;
                        }
                    }

                    var incompatibilities = new List <BepInIncompatibility>();
                    foreach (var incompatibility in pluginInfo.Incompatibilities)
                    {
                        if (processedPlugins.ContainsKey(incompatibility.IncompatibilityGUID))
                        {
                            incompatibilities.Add(incompatibility);
                        }
                    }

                    processedPlugins.Add(pluginGUID, pluginInfo.Metadata.Version);

                    if (dependsOnInvalidPlugin)
                    {
                        string message = $"Skipping [{pluginInfo.Metadata.Name}] because it has a dependency that was not loaded. See above errors for details.";
                        DependencyErrors.Add(message);
                        Logger.LogWarning(message);
                        continue;
                    }

                    if (missingDependencies.Count != 0)
                    {
                        string ToMissingString(BepInDependency s)
                        {
                            bool emptyVersion = s.MinimumVersion.Major == 0 && s.MinimumVersion.Minor == 0 && s.MinimumVersion.Build == 0 && s.MinimumVersion.Revision == 0;

                            if (emptyVersion)
                            {
                                return("- " + s.DependencyGUID);
                            }
                            return($"- {s.DependencyGUID} (at least v{s.MinimumVersion})");
                        }

                        string message = $@"Could not load [{pluginInfo.Metadata.Name}] because it has missing dependencies: {string.Join(", ", missingDependencies.Select(ToMissingString).ToArray())}";
                        DependencyErrors.Add(message);
                        Logger.LogError(message);

                        invalidPlugins.Add(pluginGUID);
                        continue;
                    }

                    if (incompatibilities.Count != 0)
                    {
                        string message = $@"Could not load [{pluginInfo.Metadata.Name}] because it is incompatible with: {string.Join(", ", incompatibilities.Select(i => i.IncompatibilityGUID).ToArray())}";
                        DependencyErrors.Add(message);
                        Logger.LogError(message);

                        invalidPlugins.Add(pluginGUID);
                        continue;
                    }

                    try
                    {
                        Logger.LogInfo($"Loading [{pluginInfo.Metadata.Name} {pluginInfo.Metadata.Version}]");

                        if (!loadedAssemblies.TryGetValue(pluginInfo.Location, out var ass))
                        {
                            loadedAssemblies[pluginInfo.Location] = ass = Assembly.LoadFile(pluginInfo.Location);
                        }

                        PluginInfos[pluginGUID] = pluginInfo;
                        pluginInfo.Instance     = (BaseUnityPlugin)ManagerObject.AddComponent(ass.GetType(pluginInfo.TypeName));

                        Plugins.Add(pluginInfo.Instance);
                    }
                    catch (Exception ex)
                    {
                        invalidPlugins.Add(pluginGUID);
                        PluginInfos.Remove(pluginGUID);

                        Logger.LogError($"Error loading [{pluginInfo.Metadata.Name}] : {ex.Message}");
                        if (ex is ReflectionTypeLoadException re)
                        {
                            Logger.LogDebug(TypeLoader.TypeLoadExceptionToString(re));
                        }
                        else
                        {
                            Logger.LogDebug(ex);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                ConsoleWindow.Attach();

                Console.WriteLine("Error occurred starting the game");
                Console.WriteLine(ex.ToString());
            }

            Logger.LogMessage("Chainloader startup complete");

            _loaded = true;
        }
示例#13
0
        /// <summary>
        /// The entrypoint for the BepInEx plugin system.
        /// </summary>
        public static void Start()
        {
            if (_loaded)
            {
                return;
            }

            if (!_initialized)
            {
                throw new InvalidOperationException("BepInEx has not been initialized. Please call Chainloader.Initialize prior to starting the chainloader instance.");
            }

            if (!Directory.Exists(Paths.PluginPath))
            {
                Directory.CreateDirectory(Paths.PluginPath);
            }

            if (!Directory.Exists(Paths.PatcherPluginPath))
            {
                Directory.CreateDirectory(Paths.PatcherPluginPath);
            }

            try
            {
                var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static);
                if (productNameProp != null)
                {
                    ConsoleWindow.Title =
                        $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {productNameProp.GetValue(null, null)}";
                }

                Logger.LogMessage("Chainloader started");

                ManagerObject = new GameObject("BepInEx_Manager");

                UnityEngine.Object.DontDestroyOnLoad(ManagerObject);


                string currentProcess = Process.GetCurrentProcess().ProcessName.ToLower();

                var globalPluginTypes = TypeLoader.LoadTypes <BaseUnityPlugin>(Paths.PluginPath).ToList();

                var selectedPluginTypes = globalPluginTypes
                                          .Where(plugin =>
                {
                    //Ensure metadata exists
                    var metadata = MetadataHelper.GetMetadata(plugin);

                    if (metadata == null)
                    {
                        Logger.LogWarning($"Skipping over type [{plugin.Name}] as no metadata attribute is specified");
                        return(false);
                    }

                    //Perform a filter for currently running process
                    var filters = MetadataHelper.GetAttributes <BepInProcess>(plugin);

                    if (filters.Length == 0)                                                                       //no filters means it loads everywhere
                    {
                        return(true);
                    }

                    var result = filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess);

                    if (!result)
                    {
                        Logger.LogInfo($"Skipping over plugin [{metadata.GUID}] due to process filter");
                    }

                    return(result);
                })
                                          .ToList();

                Logger.LogInfo($"{selectedPluginTypes.Count} / {globalPluginTypes.Count} plugins to load");

                Dictionary <Type, IEnumerable <Type> > dependencyDict = new Dictionary <Type, IEnumerable <Type> >();


                foreach (Type t in selectedPluginTypes)
                {
                    try
                    {
                        IEnumerable <Type> dependencies = MetadataHelper.GetDependencies(t, selectedPluginTypes);

                        dependencyDict[t] = dependencies;
                    }
                    catch (MissingDependencyException)
                    {
                        var metadata = MetadataHelper.GetMetadata(t);

                        Logger.LogWarning($"Cannot load [{metadata.Name}] due to missing dependencies.");
                    }
                }

                var sortedTypes = Utility.TopologicalSort(dependencyDict.Keys, x => dependencyDict[x]).ToList();

                foreach (Type t in sortedTypes)
                {
                    try
                    {
                        var metadata = MetadataHelper.GetMetadata(t);
                        Logger.LogInfo($"Loading [{metadata.Name} {metadata.Version}]");

                        var plugin = (BaseUnityPlugin)ManagerObject.AddComponent(t);

                        Plugins.Add(plugin);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError($"Error loading [{t.Name}] : {ex.Message}");
                        Logger.LogDebug(ex);
                    }
                }
            }
            catch (Exception ex)
            {
                ConsoleWindow.Attach();

                Console.WriteLine("Error occurred starting the game");
                Console.WriteLine(ex.ToString());
            }

            Logger.LogMessage("Chainloader startup complete");

            _loaded = true;
        }