public void PassStarted(IPass pass)
 {
     if (pass == null)
     {
         throw new ArgumentNullException(nameof(pass));
     }
     logger.Info(pass.Name + " pass");
     OnPassStarted.Fire(pass);
 }
Example #2
0
        public void RunTestCases(UrlDir gameDatabaseRoot)
        {
            if (gameDatabaseRoot == null)
            {
                throw new ArgumentNullException(nameof(gameDatabaseRoot));
            }
            logger.Info("Running tests...");

            foreach (UrlDir.UrlConfig expect in gameDatabaseRoot.GetConfigs("MMTEST_EXPECT"))
            {
                // So for each of the expects, we expect all the configs before that node to match exactly.
                UrlDir.UrlFile parent = expect.parent;
                if (parent.configs.Count != expect.config.CountNodes + 1)
                {
                    logger.Error("Test " + parent.name + " failed as expected number of nodes differs expected: " +
                                 expect.config.CountNodes + " found: " + (parent.configs.Count - 1));
                    for (int i = 0; i < parent.configs.Count; ++i)
                    {
                        logger.Info(parent.configs[i].config.ToString());
                    }
                    continue;
                }
                for (int i = 0; i < expect.config.CountNodes; ++i)
                {
                    ConfigNode gotNode    = parent.configs[i].config;
                    ConfigNode expectNode = expect.config.nodes[i];
                    if (!CompareRecursive(expectNode, gotNode))
                    {
                        logger.Error("Test " + parent.name + "[" + i +
                                     "] failed as expected output and actual output differ.\nexpected:\n" + expectNode +
                                     "\nActually got:\n" + gotNode);
                    }
                }

                // Purge the tests
                parent.configs.Clear();
            }
            logger.Info("tests complete.");
        }
Example #3
0
        public void Apply(LinkedList <IProtoUrlConfig> databaseConfigs, IPatchProgress progress, IBasicLogger logger)
        {
            if (databaseConfigs == null)
            {
                throw new ArgumentNullException(nameof(databaseConfigs));
            }
            if (progress == null)
            {
                throw new ArgumentNullException(nameof(progress));
            }
            if (logger == null)
            {
                throw new ArgumentNullException(nameof(logger));
            }

            PatchContext context = new PatchContext(UrlConfig, databaseConfigs, logger, progress);

            for (LinkedListNode <IProtoUrlConfig> listNode = databaseConfigs.First; listNode != null; listNode = listNode.Next)
            {
                IProtoUrlConfig protoConfig = listNode.Value;
                try
                {
                    if (!NodeMatcher.IsMatch(protoConfig.Node))
                    {
                        continue;
                    }
                    if (loop)
                    {
                        logger.Info($"Looping on {UrlConfig.SafeUrl()} to {protoConfig.FullUrl}");
                    }

                    do
                    {
                        progress.ApplyingUpdate(protoConfig, UrlConfig);
                        listNode.Value = protoConfig = new ProtoUrlConfig(protoConfig.UrlFile, MMPatchLoader.ModifyNode(new NodeStack(protoConfig.Node), UrlConfig.config, context));
                    } while (loop && NodeMatcher.IsMatch(protoConfig.Node));

                    if (loop)
                    {
                        protoConfig.Node.RemoveNodes("MM_PATCH_LOOP");
                    }
                }
                catch (Exception ex)
                {
                    progress.Exception(UrlConfig, $"Exception while applying update {UrlConfig.SafeUrl()} to {protoConfig.FullUrl}", ex);
                }
            }
        }
Example #4
0
        public void Apply(UrlDir.UrlFile file, IPatchProgress progress, IBasicLogger logger)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (progress == null)
            {
                throw new ArgumentNullException(nameof(progress));
            }
            if (logger == null)
            {
                throw new ArgumentNullException(nameof(logger));
            }

            PatchContext context = new PatchContext(UrlConfig, file.root, logger, progress);

            for (int i = 0; i < file.configs.Count; i++)
            {
                UrlDir.UrlConfig urlConfig = file.configs[i];
                try
                {
                    if (!NodeMatcher.IsMatch(urlConfig.config))
                    {
                        continue;
                    }
                    if (loop)
                    {
                        logger.Info($"Looping on {UrlConfig.SafeUrl()} to {urlConfig.SafeUrl()}");
                    }

                    do
                    {
                        progress.ApplyingUpdate(urlConfig, UrlConfig);
                        file.configs[i] = urlConfig = new UrlDir.UrlConfig(file, MMPatchLoader.ModifyNode(new NodeStack(urlConfig.config), UrlConfig.config, context));
                    } while (loop && NodeMatcher.IsMatch(urlConfig.config));

                    if (loop)
                    {
                        file.configs[i].config.RemoveNodes("MM_PATCH_LOOP");
                    }
                }
                catch (Exception ex)
                {
                    progress.Exception(UrlConfig, $"Exception while applying update {UrlConfig.SafeUrl()} to {urlConfig.SafeUrl()}", ex);
                }
            }
        }
Example #5
0
        public IEnumerator Run()
        {
            PostPatchLoader.Instance.databaseConfigs = null;

            if (!Directory.Exists(logsDirPath))
            {
                Directory.CreateDirectory(logsDirPath);
            }

            kspLogger.Info("Patching started on a new thread, all output will be directed to " + logPath);

            MessageQueue <ILogMessage> mmLogQueue = new MessageQueue <ILogMessage>();
            QueueLogRunner             logRunner  = new QueueLogRunner(mmLogQueue);
            ITaskStatus loggingThreadStatus       = BackgroundTask.Start(delegate
            {
                using StreamLogger streamLogger = new StreamLogger(new FileStream(logPath, FileMode.Create));
                streamLogger.Info("Log started at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
                logRunner.Run(streamLogger);
                streamLogger.Info("Done!");
            });
Example #6
0
        private void ApplyPatches(IEnumerable <UrlDir.UrlFile> configFiles, IPass pass)
        {
            logger.Info(pass.Name + " pass");
            Activity = "ModuleManager " + pass.Name;

            foreach (IPatch patch in pass)
            {
                try
                {
                    foreach (UrlDir.UrlFile file in configFiles)
                    {
                        patch.Apply(file, progress, logger);
                    }
                    progress.PatchApplied();
                }
                catch (Exception e)
                {
                    progress.Exception(patch.UrlConfig, "Exception while processing node : " + patch.UrlConfig.SafeUrl(), e);
                    logger.Error("Processed node was\n" + patch.UrlConfig.PrettyPrint());
                }
            }
        }
Example #7
0
        public IEnumerator Run()
        {
            PostPatchLoader.Instance.databaseConfigs = null;

            if (!Directory.Exists(logsDirPath))
            {
                Directory.CreateDirectory(logsDirPath);
            }

            kspLogger.Info("Patching started on a new thread, all output will be directed to " + logPath);

            MessageQueue <ILogMessage> mmLogQueue = new MessageQueue <ILogMessage>();
            QueueLogRunner             logRunner  = new QueueLogRunner(mmLogQueue);
            ITaskStatus loggingThreadStatus       = BackgroundTask.Start(delegate
            {
                using (StreamLogger streamLogger = new StreamLogger(new FileStream(logPath, FileMode.Create)))
                {
                    logRunner.Run(streamLogger);
                    streamLogger.Info("Done!");
                }
            });

            // Wait for game database to be initialized for the 2nd time and wait for any plugins to initialize
            yield return(null);

            yield return(null);

            IBasicLogger mmLogger = new QueueLogger(mmLogQueue);

            IEnumerable <ModListGenerator.ModAddedByAssembly> modsAddedByAssemblies = ModListGenerator.GetAdditionalModsFromStaticMethods(mmLogger);

            IEnumerable <IProtoUrlConfig> databaseConfigs = null;

            MMPatchLoader patchLoader = new MMPatchLoader(modsAddedByAssemblies, mmLogger);

            ITaskStatus patchingThreadStatus = BackgroundTask.Start(delegate
            {
                databaseConfigs = patchLoader.Run();
            });

            while (true)
            {
                yield return(null);

                if (!patchingThreadStatus.IsRunning)
                {
                    logRunner.RequestStop();
                }

                Status = patchLoader.status;
                Errors = patchLoader.errors;

                if (!patchingThreadStatus.IsRunning && !loggingThreadStatus.IsRunning)
                {
                    break;
                }
            }

            if (patchingThreadStatus.IsExitedWithError)
            {
                kspLogger.Exception("The patching thread threw an exception", patchingThreadStatus.Exception);
                FatalErrorHandler.HandleFatalError("The patching thread threw an exception");
            }

            if (loggingThreadStatus.IsExitedWithError)
            {
                kspLogger.Exception("The logging thread threw an exception", loggingThreadStatus.Exception);
                FatalErrorHandler.HandleFatalError("The logging thread threw an exception");
            }

            if (databaseConfigs == null)
            {
                kspLogger.Error("The patcher returned a null collection of configs");
                FatalErrorHandler.HandleFatalError("The patcher returned a null collection of configs");
                yield break;
            }

            PostPatchLoader.Instance.databaseConfigs = databaseConfigs;
        }
Example #8
0
        private void ApplyPatches(string stage, IEnumerable <UrlDir.UrlConfig> patches)
        {
            logger.Info(stage + " pass");
            Activity = "ModuleManager " + stage;

            foreach (UrlDir.UrlConfig mod in patches)
            {
                try
                {
                    string  name = mod.type.RemoveWS();
                    Command cmd  = CommandParser.Parse(name, out string tmp);

                    if (cmd == Command.Insert)
                    {
                        logger.Warning("Warning - Encountered insert node that should not exist at this stage: " + mod.SafeUrl());
                        continue;
                    }
                    else if (cmd != Command.Edit && cmd != Command.Copy && cmd != Command.Delete)
                    {
                        logger.Warning("Invalid command encountered on a patch: " + mod.SafeUrl());
                        continue;
                    }

                    string       upperName = name.ToUpper();
                    PatchContext context   = new PatchContext(mod, databaseRoot, logger, progress);
                    char[]       sep       = { '[', ']' };
                    string       condition = "";

                    if (upperName.Contains(":HAS["))
                    {
                        int start = upperName.IndexOf(":HAS[");
                        condition = name.Substring(start + 5, name.LastIndexOf(']') - start - 5);
                        name      = name.Substring(0, start);
                    }

                    string[] splits   = name.Split(sep, 3);
                    string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : null;
                    string   type     = splits[0].Substring(1);

                    bool loop = mod.config.HasNode("MM_PATCH_LOOP");

                    foreach (UrlDir.UrlFile file in allConfigFiles)
                    {
                        if (cmd == Command.Edit)
                        {
                            foreach (UrlDir.UrlConfig url in file.configs)
                            {
                                if (!IsMatch(url, type, patterns, condition))
                                {
                                    continue;
                                }
                                if (loop)
                                {
                                    logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl());
                                }

                                do
                                {
                                    progress.ApplyingUpdate(url, mod);
                                    url.config = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context);
                                } while (loop && IsMatch(url, type, patterns, condition));

                                if (loop)
                                {
                                    url.config.RemoveNodes("MM_PATCH_LOOP");
                                }
                            }
                        }
                        else if (cmd == Command.Copy)
                        {
                            // Avoid checking the new configs we are creating
                            int count = file.configs.Count;
                            for (int i = 0; i < count; i++)
                            {
                                UrlDir.UrlConfig url = file.configs[i];
                                if (!IsMatch(url, type, patterns, condition))
                                {
                                    continue;
                                }

                                ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context);
                                if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name"))
                                {
                                    progress.Error(mod, $"Error - when applying copy {mod.SafeUrl()} to {url.SafeUrl()} - the copy needs to have a different name than the parent (use @name = xxx)");
                                }
                                else
                                {
                                    progress.ApplyingCopy(url, mod);
                                    file.AddConfig(clone);
                                }
                            }
                        }
                        else if (cmd == Command.Delete)
                        {
                            int i = 0;
                            while (i < file.configs.Count)
                            {
                                UrlDir.UrlConfig url = file.configs[i];

                                if (IsMatch(url, type, patterns, condition))
                                {
                                    progress.ApplyingDelete(url, mod);
                                    file.configs.RemoveAt(i);
                                }
                                else
                                {
                                    i++;
                                }
                            }
                        }
                        else
                        {
                            throw new NotImplementedException("This code should not be reachable");
                        }
                    }
                    progress.PatchApplied();
                }
                catch (Exception e)
                {
                    progress.Exception(mod, "Exception while processing node : " + mod.SafeUrl(), e);

                    try
                    {
                        logger.Error("Processed node was\n" + mod.PrettyPrint());
                    }
                    catch (Exception ex2)
                    {
                        logger.Exception("Exception while attempting to print a node", ex2);
                    }
                }
            }
        }
Example #9
0
        private IEnumerator Run()
        {
            Stopwatch waitTimer = new Stopwatch();

            waitTimer.Start();

            progressTitle = "ModuleManager: Waiting for patching to finish";

            while (databaseConfigs == null)
            {
                yield return(null);
            }

            waitTimer.Stop();
            logger.Info("Waited " + ((float)waitTimer.ElapsedMilliseconds / 1000).ToString("F3") + "s for patching to finish");

            Stopwatch postPatchTimer = new Stopwatch();

            postPatchTimer.Start();

            progressTitle = "ModuleManager: Applying patched game database";
            logger.Info("Applying patched game database");

            foreach (UrlDir.UrlFile file in GameDatabase.Instance.root.AllConfigFiles)
            {
                file.configs.Clear();
            }

            foreach (IProtoUrlConfig protoConfig in databaseConfigs)
            {
                protoConfig.UrlFile.AddConfig(protoConfig.Node);
            }

            databaseConfigs = null;

            yield return(null);

#if false
            // Using an dedicated external log is nice. Dumping it into KSP.log breaking the known formats is not.
            if (File.Exists(logPath))
            {
                progressTitle = "ModuleManager: Dumping log to KSP log";
                logger.Info("Dumping ModuleManager log to main log");
                logger.Info("\n#### BEGIN MODULEMANAGER LOG ####\n\n\n" + File.ReadAllText(logPath) + "\n\n\n#### END MODULEMANAGER LOG ####");
            }
            else
            {
                logger.Error("ModuleManager log does not exist: " + logPath);
            }
#endif
            yield return(null);

#if DEBUG
            InGameTestRunner testRunner = new InGameTestRunner(logger);
            testRunner.RunTestCases(GameDatabase.Instance.root);
#endif

            yield return(null);

            progressTitle = "ModuleManager: Reloading things";

            logger.Info("Reloading resources definitions");
            PartResourceLibrary.Instance.LoadDefinitions();

            logger.Info("Reloading Trait configs");
            GameDatabase.Instance.ExperienceConfigs.LoadTraitConfigs();

            logger.Info("Reloading Part Upgrades");
            PartUpgradeManager.Handler.FillUpgrades();

            yield return(null);

            progressTitle = "ModuleManager: Running post patch callbacks";
            logger.Info("Running post patch callbacks");

            foreach (ModuleManagerPostPatchCallback callback in postPatchCallbacks)
            {
                try
                {
                    callback();
                }
                catch (Exception e)
                {
                    logger.Exception("Exception while running a post patch callback", e);
                }
                yield return(null);
            }
            yield return(null);

            // Call all "public static void ModuleManagerPostLoad()" on all class
            foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies())
            {
                try
                {
                    foreach (Type type in ass.GetTypes())
                    {
                        MethodInfo method = type.GetMethod("ModuleManagerPostLoad", BindingFlags.Public | BindingFlags.Static);

                        if (method != null && method.GetParameters().Length == 0)
                        {
                            try
                            {
                                logger.Info("Calling " + ass.GetName().Name + "." + type.Name + "." + method.Name + "()");
                                method.Invoke(null, null);
                            }
                            catch (Exception e)
                            {
                                logger.Exception("Exception while calling " + ass.GetName().Name + "." + type.Name + "." + method.Name + "()", e);
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    logger.Exception("Post run call threw an exception in loading " + ass.FullName, e);
                }
            }

            yield return(null);

            // Call "public void ModuleManagerPostLoad()" on all active MonoBehaviour instance
            foreach (MonoBehaviour obj in FindObjectsOfType <MonoBehaviour>())
            {
                MethodInfo method = obj.GetType().GetMethod("ModuleManagerPostLoad", BindingFlags.Public | BindingFlags.Instance);

                if (method != null && method.GetParameters().Length == 0)
                {
                    try
                    {
                        logger.Info("Calling " + obj.GetType().Name + "." + method.Name + "()");
                        method.Invoke(obj, null);
                    }
                    catch (Exception e)
                    {
                        logger.Exception("Exception while calling " + obj.GetType().Name + "." + method.Name + "() :", e);
                    }
                }
            }

            yield return(null);

            if (ModuleManager.dumpPostPatch)
            {
                ModuleManager.OutputAllConfigs();
            }

            postPatchTimer.Stop();
            logger.Info("Post patch ran in " + ((float)postPatchTimer.ElapsedMilliseconds / 1000).ToString("F3") + "s");

            ready = true;
        }
Example #10
0
        public static IEnumerable <ModAddedByAssembly> GetAdditionalModsFromStaticMethods(IBasicLogger logger)
        {
            List <ModAddedByAssembly> result = new List <ModAddedByAssembly>();

            foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies())
            {
                try
                {
                    foreach (Type type in ass.GetTypes())
                    {
                        MethodInfo method = type.GetMethod("ModuleManagerAddToModList", BindingFlags.Public | BindingFlags.Static);

                        if (method != null && method.GetParameters().Length == 0 && typeof(IEnumerable <string>).IsAssignableFrom(method.ReturnType))
                        {
                            string methodName = $"{ass.GetName().Name}.{type.Name}.{method.Name}()";
                            try
                            {
                                logger.Info("Calling " + methodName);
                                IEnumerable <string> modsToAdd = (IEnumerable <string>)method.Invoke(null, null);

                                if (modsToAdd == null)
                                {
                                    logger.Error("ModuleManagerAddToModList returned null: " + methodName);
                                    continue;
                                }

                                foreach (string mod in modsToAdd)
                                {
                                    result.Add(new ModAddedByAssembly(mod, ass.GetName().Name));
                                }
                            }
                            catch (Exception e)
                            {
                                logger.Exception("Exception while calling " + methodName, e);
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    logger.Exception("Add to mod list threw an exception in loading " + ass.FullName, e);
                }
            }

            foreach (MonoBehaviour obj in UnityEngine.Object.FindObjectsOfType <MonoBehaviour>())
            {
                MethodInfo method = obj.GetType().GetMethod("ModuleManagerAddToModList", BindingFlags.Public | BindingFlags.Instance);

                if (method != null && method.GetParameters().Length == 0 && typeof(IEnumerable <string>).IsAssignableFrom(method.ReturnType))
                {
                    string methodName = $"{obj.GetType().Name}.{method.Name}()";
                    try
                    {
                        logger.Info("Calling " + methodName);
                        IEnumerable <string> modsToAdd = (IEnumerable <string>)method.Invoke(obj, null);

                        if (modsToAdd == null)
                        {
                            logger.Error("ModuleManagerAddToModList returned null: " + methodName);
                            continue;
                        }

                        foreach (string mod in modsToAdd)
                        {
                            result.Add(new ModAddedByAssembly(mod, obj.GetType().Assembly.GetName().Name));
                        }
                    }
                    catch (Exception e)
                    {
                        logger.Exception("Exception while calling " + methodName, e);
                    }
                }
            }

            return(result);
        }
Example #11
0
        public static IEnumerable <string> GenerateModList(IEnumerable <ModAddedByAssembly> modsAddedByAssemblies, IPatchProgress progress, IBasicLogger logger)
        {
            #region List of mods

            //string envInfo = "ModuleManager env info\n";
            //envInfo += "  " + Environment.OSVersion.Platform + " " + ModuleManager.intPtr.ToInt64().ToString("X16") + "\n";
            //envInfo += "  " + Convert.ToString(ModuleManager.intPtr.ToInt64(), 2)  + " " + Convert.ToString(ModuleManager.intPtr.ToInt64() >> 63, 2) + "\n";
            //string gamePath = Environment.GetCommandLineArgs()[0];
            //envInfo += "  Args: " + gamePath.Split(Path.DirectorySeparatorChar).Last() + " " + string.Join(" ", Environment.GetCommandLineArgs().Skip(1).ToArray()) + "\n";
            //envInfo += "  Executable SHA256 " + FileSHA(gamePath);
            //
            //log(envInfo);

            List <string> mods = new List <string>();

            StringBuilder modListInfo = new StringBuilder();

            modListInfo.Append("compiling list of loaded mods...\nMod DLLs found:\n");

            string format = "  {0,-40}{1,-25}{2,-25}{3,-25}{4}\n";

            modListInfo.AppendFormat(
                format,
                "Name",
                "Assembly Version",
                "Assembly File Version",
                "KSPAssembly Version",
                "SHA256"
                );

            modListInfo.Append('\n');

            foreach (AssemblyLoader.LoadedAssembly mod in AssemblyLoader.loadedAssemblies)
            {
                if (string.IsNullOrEmpty(mod.assembly.Location)) //Diazo Edit for xEvilReeperx AssemblyReloader mod
                {
                    continue;
                }

                FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(mod.assembly.Location);

                AssemblyName assemblyName = mod.assembly.GetName();

                string kspAssemblyVersion;
                if (mod.versionMajor == 0 && mod.versionMinor == 0)
                {
                    kspAssemblyVersion = "";
                }
                else
                {
                    kspAssemblyVersion = mod.versionMajor + "." + mod.versionMinor;
                }

                string fileSha = "";
                try
                {
                    fileSha = FileUtils.FileSHA(mod.assembly.Location);
                }
                catch (Exception e)
                {
                    progress.Exception("Exception while generating SHA for assembly " + assemblyName.Name, e);
                }

                modListInfo.AppendFormat(
                    format,
                    assemblyName.Name,
                    assemblyName.Version,
                    fileVersionInfo.FileVersion,
                    kspAssemblyVersion,
                    fileSha
                    );

                // modlist += String.Format("  {0,-50} SHA256 {1}\n", modInfo, FileSHA(mod.assembly.Location));

                if (!mods.Contains(assemblyName.Name, StringComparer.OrdinalIgnoreCase))
                {
                    mods.Add(assemblyName.Name);
                }
            }

            modListInfo.Append("Non-DLL mods added (:FOR[xxx]):\n");
            foreach (UrlDir.UrlConfig cfgmod in GameDatabase.Instance.root.AllConfigs)
            {
                if (CommandParser.Parse(cfgmod.type, out string name) != Command.Insert)
                {
                    if (name.Contains(":FOR["))
                    {
                        name = name.RemoveWS();

                        // check for FOR[] blocks that don't match loaded DLLs and add them to the pass list
                        try
                        {
                            string dependency = name.Substring(name.IndexOf(":FOR[") + 5);
                            dependency = dependency.Substring(0, dependency.IndexOf(']'));
                            if (!mods.Contains(dependency, StringComparer.OrdinalIgnoreCase))
                            {
                                // found one, now add it to the list.
                                mods.Add(dependency);
                                modListInfo.AppendFormat("  {0}\n", dependency);
                            }
                        }
                        catch (ArgumentOutOfRangeException)
                        {
                            progress.Error(cfgmod, "Skipping :FOR init for line " + name +
                                           ". The line most likely contains a space that should be removed");
                        }
                    }
                }
            }
            modListInfo.Append("Mods by directory (sub directories of GameData):\n");
            UrlDir gameData = GameDatabase.Instance.root.children.First(dir => dir.type == UrlDir.DirectoryType.GameData);
            foreach (UrlDir subDir in gameData.children)
            {
                string cleanName = subDir.name.RemoveWS();
                if (!mods.Contains(cleanName, StringComparer.OrdinalIgnoreCase))
                {
                    mods.Add(cleanName);
                    modListInfo.AppendFormat("  {0}\n", cleanName);
                }
            }

            modListInfo.Append("Mods added by assemblies:\n");
            foreach (ModAddedByAssembly mod in modsAddedByAssemblies)
            {
                if (!mods.Contains(mod.modName, StringComparer.OrdinalIgnoreCase))
                {
                    mods.Add(mod.modName);
                    modListInfo.AppendFormat("  {0}\n", mod);
                }
            }

            logger.Info(modListInfo.ToString());

            mods.Sort();

            #endregion List of mods

            return(mods);
        }
 public void TestInfo()
 {
     logger.Info("well hi there");
     logger.AssertInfo("well hi there");
 }
 public void TestInfo()
 {
     logger.Info("well hi there");
     logger.Received().Log(LogType.Log, "well hi there");
 }
 public void ApplyingUpdate(UrlDir.UrlConfig original, UrlDir.UrlConfig patch)
 {
     logger.Info($"Applying update {patch.SafeUrl()} to {original.SafeUrl()}");
     Counter.patchedNodes.Increment();
 }