Example #1
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);

            // 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];
                try
                {
                    if (!NodeMatcher.IsMatch(url.config))
                    {
                        continue;
                    }

                    ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), UrlConfig.config, context);
                    if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name"))
                    {
                        progress.Error(UrlConfig, $"Error - when applying copy {UrlConfig.SafeUrl()} to {url.SafeUrl()} - the copy needs to have a different name than the parent (use @name = xxx)");
                    }
                    else
                    {
                        progress.ApplyingCopy(url, UrlConfig);
                        file.AddConfig(clone);
                    }
                }
                catch (Exception ex)
                {
                    progress.Exception(UrlConfig, $"Exception while applying copy {UrlConfig.SafeUrl()} to {url.SafeUrl()}", ex);
                }
            }
        }
Example #2
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;
                    }

                    ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(protoConfig.Node), UrlConfig.config, context);
                    if (protoConfig.Node.GetValue("name") is string name && name == clone.GetValue("name"))
                    {
                        progress.Error(UrlConfig, $"Error - when applying copy {UrlConfig.SafeUrl()} to {protoConfig.FullUrl} - the copy needs to have a different name than the parent (use @name = xxx)");
                    }
                    else
                    {
                        progress.ApplyingCopy(protoConfig, UrlConfig);
                        listNode = databaseConfigs.AddAfter(listNode, new ProtoUrlConfig(protoConfig.UrlFile, clone));
                    }
                }
Example #3
0
        public IPatch ExtractPatch(UrlDir.UrlConfig urlConfig)
        {
            if (urlConfig == null)
            {
                throw new ArgumentNullException(nameof(urlConfig));
            }

            try
            {
                if (!urlConfig.type.IsBracketBalanced())
                {
                    progress.Error(urlConfig, "Error - node name does not have balanced brackets (or a space - if so replace with ?):\n" + urlConfig.SafeUrl());
                    return(null);
                }

                Command command = CommandParser.Parse(urlConfig.type, out string name);

                if (command == Command.Replace)
                {
                    progress.Error(urlConfig, $"Error - replace command (%) is not valid on a root node: {urlConfig.SafeUrl()}");
                    return(null);
                }
                else if (command == Command.Create)
                {
                    progress.Error(urlConfig, $"Error - create command (&) is not valid on a root node: {urlConfig.SafeUrl()}");
                    return(null);
                }
                else if (command == Command.Rename)
                {
                    progress.Error(urlConfig, $"Error - rename command (|) is not valid on a root node: {urlConfig.SafeUrl()}");
                    return(null);
                }
                else if (command == Command.Paste)
                {
                    progress.Error(urlConfig, $"Error - paste command (#) is not valid on a root node: {urlConfig.SafeUrl()}");
                    return(null);
                }
                else if (command == Command.Special)
                {
                    progress.Error(urlConfig, $"Error - special command (*) is not valid on a root node: {urlConfig.SafeUrl()}");
                    return(null);
                }

                ITagList tagList;
                try
                {
                    tagList = tagListParser.Parse(name, urlConfig);
                }
                catch (FormatException ex)
                {
                    progress.Error(urlConfig, $"Cannot parse node name as tag list: {ex.Message}\non: {urlConfig.SafeUrl()}");
                    return(null);
                }

                ProtoPatch protoPatch = protoPatchBuilder.Build(urlConfig, command, tagList);

                if (protoPatch == null)
                {
                    return(null);
                }

                if (protoPatch.needs != null && !needsChecker.CheckNeedsExpression(protoPatch.needs))
                {
                    progress.NeedsUnsatisfiedRoot(urlConfig);
                    return(null);
                }
                else if (!protoPatch.passSpecifier.CheckNeeds(needsChecker, progress))
                {
                    return(null);
                }

                needsChecker.CheckNeedsRecursive(urlConfig.config, urlConfig);
                return(patchCompiler.CompilePatch(protoPatch));
            }
            catch (Exception e)
            {
                progress.Exception(urlConfig, $"Exception while attempting to create patch from config: {urlConfig.SafeUrl()}", e);
                return(null);
            }
        }
Example #4
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);
                    }
                }
            }
        }
        private void CheckNeedsRecursive(NodeStack nodeStack, UrlDir.UrlConfig urlConfig)
        {
            ConfigNode original = nodeStack.value;

            for (int i = 0; i < original.values.Count; ++i)
            {
                ConfigNode.Value val     = original.values[i];
                string           valname = val.name;
                try
                {
                    if (CheckNeedsName(ref valname))
                    {
                        val.name = valname;
                    }
                    else
                    {
                        original.values.Remove(val);
                        i--;
                        progress.NeedsUnsatisfiedValue(urlConfig, nodeStack.GetPath() + '/' + val.name);
                    }
                }
                catch (ArgumentOutOfRangeException e)
                {
                    progress.Exception("ArgumentOutOfRangeException in CheckNeeds for value \"" + val.name + "\"", e);
                    throw;
                }
                catch (Exception e)
                {
                    progress.Exception("General Exception in CheckNeeds for value \"" + val.name + "\"", e);
                    throw;
                }
            }

            for (int i = 0; i < original.nodes.Count; ++i)
            {
                ConfigNode node     = original.nodes[i];
                string     nodeName = node.name;

                if (nodeName == null)
                {
                    progress.Error(urlConfig, "Error - Node in file " + urlConfig.SafeUrl() + " subnode: " + nodeStack.GetPath() + " has config.name == null");
                }

                try
                {
                    if (CheckNeedsName(ref nodeName))
                    {
                        node.name = nodeName;
                        CheckNeedsRecursive(nodeStack.Push(node), urlConfig);
                    }
                    else
                    {
                        original.nodes.Remove(node);
                        i--;
                        progress.NeedsUnsatisfiedNode(urlConfig, nodeStack.GetPath() + '/' + node.name);
                    }
                }
                catch (ArgumentOutOfRangeException e)
                {
                    progress.Exception("ArgumentOutOfRangeException in CheckNeeds for node \"" + node.name + "\"", e);
                    throw;
                }
                catch (Exception e)
                {
                    progress.Exception("General Exception " + e.GetType().Name + " for node \"" + node.name + "\"", e);
                    throw;
                }
            }
        }
Example #6
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 ProtoPatch Build(UrlDir.UrlConfig urlConfig, Command command, ITagList tagList)
        {
            if (urlConfig == null)
            {
                throw new ArgumentNullException(nameof(urlConfig));
            }
            if (tagList == null)
            {
                throw new ArgumentNullException(nameof(tagList));
            }
            if (progress == null)
            {
                throw new ArgumentNullException(nameof(progress));
            }

            bool error = false;

            string nodeType = tagList.PrimaryTag.key;
            string nodeName = tagList.PrimaryTag.value;

            if (command == Command.Insert && nodeName != null)
            {
                progress.Error(urlConfig, "name specifier detected on insert node (not a patch): " + urlConfig.SafeUrl());
                error = true;
            }

            if (nodeName == string.Empty)
            {
                progress.Warning(urlConfig, "empty brackets detected on patch name: " + urlConfig.SafeUrl());
                nodeName = null;
            }

            if (tagList.PrimaryTag.trailer != null)
            {
                progress.Warning(urlConfig, "unrecognized trailer: '" + tagList.PrimaryTag.trailer + "' on: " + urlConfig.SafeUrl());
            }

            string         needs         = null;
            string         has           = null;
            IPassSpecifier passSpecifier = null;

            foreach (Tag tag in tagList)
            {
                if (tag.trailer != null)
                {
                    progress.Warning(urlConfig, "unrecognized trailer: '" + tag.trailer + "' on: " + urlConfig.SafeUrl());
                }

                if (tag.key.Equals("NEEDS", StringComparison.CurrentCultureIgnoreCase))
                {
                    if (needs != null)
                    {
                        progress.Warning(urlConfig, "more than one :NEEDS tag detected, ignoring all but the first: " + urlConfig.SafeUrl());
                        continue;
                    }
                    if (string.IsNullOrEmpty(tag.value))
                    {
                        progress.Error(urlConfig, "empty :NEEDS tag detected: " + urlConfig.SafeUrl());
                        error = true;
                        continue;
                    }

                    needs = tag.value;
                }
                else if (tag.key.Equals("HAS", StringComparison.CurrentCultureIgnoreCase))
                {
                    if (command == Command.Insert)
                    {
                        progress.Error(urlConfig, ":HAS detected on insert node (not a patch): " + urlConfig.SafeUrl());
                        error = true;
                        continue;
                    }
                    if (has != null)
                    {
                        progress.Warning(urlConfig, "more than one :HAS tag detected, ignoring all but the first: " + urlConfig.SafeUrl());
                        continue;
                    }
                    if (string.IsNullOrEmpty(tag.value))
                    {
                        progress.Error(urlConfig, "empty :HAS tag detected: " + urlConfig.SafeUrl());
                        error = true;
                        continue;
                    }

                    has = tag.value;
                }
                else if (tag.key.Equals("FIRST", StringComparison.CurrentCultureIgnoreCase))
                {
                    if (tag.value != null)
                    {
                        progress.Warning(urlConfig, "value detected on :FIRST tag: " + urlConfig.SafeUrl());
                    }

                    if (command == Command.Insert)
                    {
                        progress.Error(urlConfig, "pass specifier detected on insert node (not a patch): " + urlConfig.SafeUrl());
                        error = true;
                        continue;
                    }
                    if (passSpecifier != null)
                    {
                        progress.Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: " + urlConfig.SafeUrl());
                        continue;
                    }

                    passSpecifier = new FirstPassSpecifier();
                }
                else if (tag.key.Equals("BEFORE", StringComparison.CurrentCultureIgnoreCase))
                {
                    if (string.IsNullOrEmpty(tag.value))
                    {
                        progress.Error(urlConfig, "empty :BEFORE tag detected: " + urlConfig.SafeUrl());
                        error = true;
                        continue;
                    }

                    if (command == Command.Insert)
                    {
                        progress.Error(urlConfig, "pass specifier detected on insert node (not a patch): " + urlConfig.SafeUrl());
                        error = true;
                        continue;
                    }
                    if (passSpecifier != null)
                    {
                        progress.Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: " + urlConfig.SafeUrl());
                        continue;
                    }

                    passSpecifier = new BeforePassSpecifier(tag.value, urlConfig);
                }
                else if (tag.key.Equals("FOR", StringComparison.CurrentCultureIgnoreCase))
                {
                    if (string.IsNullOrEmpty(tag.value))
                    {
                        progress.Error(urlConfig, "empty :FOR tag detected: " + urlConfig.SafeUrl());
                        error = true;
                        continue;
                    }

                    if (command == Command.Insert)
                    {
                        progress.Error(urlConfig, "pass specifier detected on insert node (not a patch): " + urlConfig.SafeUrl());
                        error = true;
                        continue;
                    }
                    if (passSpecifier != null)
                    {
                        progress.Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: " + urlConfig.SafeUrl());
                        continue;
                    }

                    passSpecifier = new ForPassSpecifier(tag.value, urlConfig);
                }
                else if (tag.key.Equals("AFTER", StringComparison.CurrentCultureIgnoreCase))
                {
                    if (string.IsNullOrEmpty(tag.value))
                    {
                        progress.Error(urlConfig, "empty :AFTER tag detected: " + urlConfig.SafeUrl());
                        error = true;
                        continue;
                    }

                    if (command == Command.Insert)
                    {
                        progress.Error(urlConfig, "pass specifier detected on insert node (not a patch): " + urlConfig.SafeUrl());
                        error = true;
                        continue;
                    }
                    if (passSpecifier != null)
                    {
                        progress.Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: " + urlConfig.SafeUrl());
                        continue;
                    }

                    passSpecifier = new AfterPassSpecifier(tag.value, urlConfig);
                }
                else if (tag.key.Equals("LAST", StringComparison.CurrentCultureIgnoreCase))
                {
                    if (string.IsNullOrEmpty(tag.value))
                    {
                        progress.Error(urlConfig, "empty :LAST tag detected: " + urlConfig.SafeUrl());
                        error = true;
                        continue;
                    }

                    if (command == Command.Insert)
                    {
                        progress.Error(urlConfig, "pass specifier detected on insert node (not a patch): " + urlConfig.SafeUrl());
                        error = true;
                        continue;
                    }
                    if (passSpecifier != null)
                    {
                        progress.Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: " + urlConfig.SafeUrl());
                        continue;
                    }

                    passSpecifier = new LastPassSpecifier(tag.value);
                }
                else if (tag.key.Equals("FINAL", StringComparison.CurrentCultureIgnoreCase))
                {
                    if (tag.value != null)
                    {
                        progress.Warning(urlConfig, "value detected on :FINAL tag: " + urlConfig.SafeUrl());
                    }

                    if (command == Command.Insert)
                    {
                        progress.Error(urlConfig, "pass specifier detected on insert node (not a patch): " + urlConfig.SafeUrl());
                        error = true;
                        continue;
                    }
                    if (passSpecifier != null)
                    {
                        progress.Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: " + urlConfig.SafeUrl());
                        continue;
                    }

                    passSpecifier = new FinalPassSpecifier();
                }
                else
                {
                    progress.Warning(urlConfig, "unrecognized tag: '" + tag.key + "' on: " + urlConfig.SafeUrl());
                }
            }

            if (error)
            {
                return(null);
            }

            if (passSpecifier == null)
            {
                if (command == Command.Insert)
                {
                    passSpecifier = new InsertPassSpecifier();
                }
                else
                {
                    passSpecifier = new LegacyPassSpecifier();
                }
            }

            return(new ProtoPatch(urlConfig, command, nodeType, nodeName, needs, has, passSpecifier));
        }
Example #8
0
        public static void CheckNeeds(UrlDir gameDatabaseRoot, IEnumerable <string> mods, IPatchProgress progress, IBasicLogger logger)
        {
            UrlDir gameData = gameDatabaseRoot.children.First(dir => dir.type == UrlDir.DirectoryType.GameData && dir.name == "");

            foreach (UrlDir.UrlConfig mod in gameDatabaseRoot.AllConfigs.ToArray())
            {
                UrlDir.UrlConfig currentMod = mod;
                try
                {
                    if (mod.config.name == null)
                    {
                        progress.Error(currentMod, "Error - Node in file " + currentMod.parent.url + " subnode: " + currentMod.type +
                                       " has config.name == null");
                    }

                    UrlDir.UrlConfig newMod;

                    if (currentMod.type.IndexOf(":NEEDS[", StringComparison.OrdinalIgnoreCase) >= 0)
                    {
                        string type = currentMod.type;

                        if (CheckNeeds(ref type, mods, gameData))
                        {
                            ConfigNode copy = new ConfigNode(type);
                            copy.ShallowCopyFrom(currentMod.config);
                            int index = mod.parent.configs.IndexOf(currentMod);
                            newMod = new UrlDir.UrlConfig(currentMod.parent, copy);
                            mod.parent.configs[index] = newMod;
                        }
                        else
                        {
                            progress.NeedsUnsatisfiedRoot(currentMod);
                            mod.parent.configs.Remove(currentMod);
                            continue;
                        }
                    }
                    else
                    {
                        newMod = currentMod;
                    }

                    // Recursively check the contents
                    PatchContext context = new PatchContext(newMod, gameDatabaseRoot, logger, progress);
                    CheckNeeds(new NodeStack(newMod.config), context, mods, gameData);
                }
                catch (Exception ex)
                {
                    try
                    {
                        mod.parent.configs.Remove(currentMod);
                    }
                    catch (Exception ex2)
                    {
                        logger.Exception("Exception while attempting to ensure config removed", ex2);
                    }

                    try
                    {
                        progress.Exception(mod, "Exception while checking needs on root node :\n" + mod.PrettyPrint(), ex);
                    }
                    catch (Exception ex2)
                    {
                        progress.Exception("Exception while attempting to log an exception", ex2);
                    }
                }
            }
        }
        public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable <string> modList, IPatchProgress progress)
        {
            PatchList list = new PatchList(modList);

            // Have to convert to an array because we will be removing patches
            foreach (UrlDir.UrlConfig url in databaseRoot.AllConfigs.ToArray())
            {
                try
                {
                    if (!url.type.IsBracketBalanced())
                    {
                        progress.Error(url, "Error - node name does not have balanced brackets (or a space - if so replace with ?):\n" + url.SafeUrl());
                        url.parent.configs.Remove(url);
                        continue;
                    }

                    Command command = CommandParser.Parse(url.type, out _);;

                    Match firstMatch  = firstRegex.Match(url.type);
                    Match finalMatch  = finalRegex.Match(url.type);
                    Match beforeMatch = beforeRegex.Match(url.type);
                    Match forMatch    = forRegex.Match(url.type);
                    Match afterMatch  = afterRegex.Match(url.type);

                    int matchCount = 0;

                    if (firstMatch.Success)
                    {
                        matchCount++;
                    }
                    if (finalMatch.Success)
                    {
                        matchCount++;
                    }
                    if (beforeMatch.Success)
                    {
                        matchCount++;
                    }
                    if (forMatch.Success)
                    {
                        matchCount++;
                    }
                    if (afterMatch.Success)
                    {
                        matchCount++;
                    }

                    if (firstMatch.NextMatch().Success)
                    {
                        matchCount++;
                    }
                    if (finalMatch.NextMatch().Success)
                    {
                        matchCount++;
                    }
                    if (beforeMatch.NextMatch().Success)
                    {
                        matchCount++;
                    }
                    if (forMatch.NextMatch().Success)
                    {
                        matchCount++;
                    }
                    if (afterMatch.NextMatch().Success)
                    {
                        matchCount++;
                    }

                    bool error = false;

                    if (command == Command.Insert && matchCount > 0)
                    {
                        progress.Error(url, $"Error - pass specifier detected on an insert node (not a patch): {url.SafeUrl()}");
                        error = true;
                    }
                    else if (command == Command.Replace)
                    {
                        progress.Error(url, $"Error - replace command (%) is not valid on a root node: {url.SafeUrl()}");
                        error = true;
                    }
                    else if (command == Command.Create)
                    {
                        progress.Error(url, $"Error - create command (&) is not valid on a root node: {url.SafeUrl()}");
                        error = true;
                    }
                    else if (command == Command.Rename)
                    {
                        progress.Error(url, $"Error - rename command (|) is not valid on a root node: {url.SafeUrl()}");
                        error = true;
                    }
                    else if (command == Command.Paste)
                    {
                        progress.Error(url, $"Error - paste command (#) is not valid on a root node: {url.SafeUrl()}");
                        error = true;
                    }
                    else if (command == Command.Special)
                    {
                        progress.Error(url, $"Error - special command (*) is not valid on a root node: {url.SafeUrl()}");
                        error = true;
                    }

                    if (matchCount > 1)
                    {
                        progress.Error(url, $"Error - more than one pass specifier on a node: {url.SafeUrl()}");
                        error = true;
                    }
                    if (beforeMatch.Success && !beforeMatch.Groups[1].Success)
                    {
                        progress.Error(url, "Error - malformed :BEFORE patch specifier detected: " + url.SafeUrl());
                        error = true;
                    }
                    if (forMatch.Success && !forMatch.Groups[1].Success)
                    {
                        progress.Error(url, "Error - malformed :FOR patch specifier detected: " + url.SafeUrl());
                        error = true;
                    }
                    if (afterMatch.Success && !afterMatch.Groups[1].Success)
                    {
                        progress.Error(url, "Error - malformed :AFTER patch specifier detected: " + url.SafeUrl());
                        error = true;
                    }
                    if (error)
                    {
                        url.parent.configs.Remove(url);
                        continue;
                    }

                    if (command == Command.Insert)
                    {
                        continue;
                    }

                    url.parent.configs.Remove(url);

                    Match theMatch = null;
                    List <UrlDir.UrlConfig> thePass = null;
                    bool modNotFound = false;

                    if (firstMatch.Success)
                    {
                        theMatch = firstMatch;
                        thePass  = list.firstPatches;
                    }
                    else if (finalMatch.Success)
                    {
                        theMatch = finalMatch;
                        thePass  = list.finalPatches;
                    }
                    else if (beforeMatch.Success)
                    {
                        if (CheckMod(beforeMatch, list.modPasses, out string theMod))
                        {
                            theMatch = beforeMatch;
                            thePass  = list.modPasses[theMod].beforePatches;
                        }
                        else
                        {
                            modNotFound = true;
                            progress.NeedsUnsatisfiedBefore(url);
                        }
                    }
                    else if (forMatch.Success)
                    {
                        if (CheckMod(forMatch, list.modPasses, out string theMod))
                        {
                            theMatch = forMatch;
                            thePass  = list.modPasses[theMod].forPatches;
                        }
                        else
                        {
                            modNotFound = true;
                            progress.NeedsUnsatisfiedFor(url);
                        }
                    }
                    else if (afterMatch.Success)
                    {
                        if (CheckMod(afterMatch, list.modPasses, out string theMod))
                        {
                            theMatch = afterMatch;
                            thePass  = list.modPasses[theMod].afterPatches;
                        }
                        else
                        {
                            modNotFound = true;
                            progress.NeedsUnsatisfiedAfter(url);
                        }
                    }
                    else
                    {
                        thePass = list.legacyPatches;
                    }

                    if (modNotFound)
                    {
                        continue;
                    }

                    UrlDir.UrlConfig newUrl = url;
                    if (theMatch != null)
                    {
                        string     newName = url.type.Remove(theMatch.Index, theMatch.Length);
                        ConfigNode newNode = new ConfigNode(newName)
                        {
                            id = url.config.id
                        };
                        newNode.ShallowCopyFrom(url.config);
                        newUrl = new UrlDir.UrlConfig(url.parent, newNode);
                    }

                    thePass.Add(newUrl);
                    progress.PatchAdded();
                }
                catch (Exception e)
                {
                    progress.Exception(url, $"Exception while parsing pass for config: {url.SafeUrl()}", e);
                }
            }

            return(list);
        }