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); } } }
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)); } }
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); } }
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; } } }
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)); }
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); }