/// <summary>Applies the patch to the part node.</summary> /// <param name="partNode">The part node to patch.</param> /// <param name="patch">The patch to apply.</param> /// <param name="loadContext">The context in which the part is being patched.</param> public static void PatchNode( ConfigNode partNode, ConfigNodePatch patch, LoadContext loadContext) { ArgumentGuard.NotNull(partNode, "partNode", context: patch); ArgumentGuard.NotNull(patch, "patchNode", context: patch); ArgumentGuard.OneOf(loadContext, "loadContext", new[] { LoadContext.SFS, LoadContext.Craft }, context: patch); var partName = GetPartNameFromUpgradeNode(partNode, loadContext); Preconditions.OneOf(patch.upgradeSection.partRules.action, new[] { ConfigNodePatch.PatchAction.Drop, ConfigNodePatch.PatchAction.Fix }, context: patch); var oldPartNode = partNode.CreateCopy(); var partId = GetPartId(partNode, loadContext); var partContext = "Part=" + partId[0] + "#id=" + partId[1]; ApplyPatchToNode(partNode, patch.upgradeSection.partRules, partContext, isPartCraftContext: loadContext == LoadContext.Craft); if (patch.upgradeSection.partRules.action == ConfigNodePatch.PatchAction.Fix) { for (var i = 0; i < patch.upgradeSection.moduleRules.Count; ++i) { var moduleRules = patch.upgradeSection.moduleRules[i]; var moduleContext = partContext + "#Module=" + moduleRules.name + "#Rule=" + i; ConfigNode targetModuleNode; if (moduleRules.action == ConfigNodePatch.PatchAction.Add) { DebugEx.Warning("[UpgradePipeline][{0}] Action: ADD NODE", moduleContext); targetModuleNode = partNode.AddNode("MODULE"); targetModuleNode.SetValue("name", moduleRules.name, "*** added by compatibility patch", createIfNotFound: true); } else { targetModuleNode = LookupModule(partNode, moduleRules.name); Preconditions.NotNull( targetModuleNode, message: "Cannot find module for UPGRADE", context: patch); } ApplyPatchToNode(targetModuleNode, moduleRules, moduleContext); } } if (patch.verboseLogging) { DebugEx.Warning("Part node has been patched:\nOriginal node:\n{0}\nNew node:\n{1}", oldPartNode, partNode); } }
/// <summary>Makes pacth from a config node.</summary> /// <remarks> /// The critical settings will be checked for the sane values. If a bad value found, then the /// menthod will throw. /// </remarks> /// <param name="node">The node to create from.</param> /// <param name="context"> /// The context of node loading. It can be any object, e.g. <c>UrlDir.UrlConfig</c> or /// <c>ConfigNode</c>. It's only used for logging errors to give a context for debugging. /// </param> /// <param name="url">The URL to the node in the game's DB.</param> /// <returns>The patch. It's never <c>null.</c></returns> static ConfigNodePatch MakeFromNodeInternal(ConfigNode node, object context, string url = null) { var patchNode = new ConfigNodePatch(); if (url != null) { patchNode.sourceConfigUrl = url; } ConfigAccessor.ReadFieldsFromNode(node, patchNode.GetType(), patchNode); // Sanity check of the test rules. Preconditions.ConfValueExists(patchNode.name, "node/name", context: context); Preconditions.ConfValueExists(patchNode.testSection, "node/TEST", context: context); Preconditions.ConfValueExists( patchNode.testSection.partTests, "node/TEST/PART", context: context); Preconditions.ConfValueExists( patchNode.testSection.partTests.GetValue("name"), "node/TEST/PART/name", context: context); foreach (var moduleNode in patchNode.testSection.moduleTests) { Preconditions.ConfValueExists( moduleNode.GetValue("name"), "node/TEST/MODULE/name", context: context); } // Sanity check of the upgrade rules Preconditions.ConfValueExists(patchNode.upgradeSection, "node/UPGRADE", context: context); Preconditions.ConfValueExists( patchNode.upgradeSection.partRules, "node/UPGRADE/PART", context: context); Preconditions.OneOf(patchNode.upgradeSection.partRules.action, new[] { PatchAction.Drop, PatchAction.Fix }, "node/UPGRADE/PART/action", context: context); foreach (var moduleRule in patchNode.upgradeSection.moduleRules) { Preconditions.ConfValueExists(moduleRule.name, "node/TEST/MODULE/name", context: context); } // Check the patch age. var patchAgeDays = (DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalDays - patchNode.patchCreationTimestamp / (24 * 60 * 60); if (patchAgeDays > 180) { DebugEx.Warning("Patch is too old: patch={0}, age={1} days", patchNode, patchAgeDays); } return(patchNode); }
/// <summary>Returns patch nodes for the tag.</summary> /// <remarks> /// This call can be very expensive. It's strongly encouraged to implement a lazy access approach /// and cache the returned values. /// </remarks> /// <param name="modName"> /// The mod to find the nodes for. If it's <c>null</c> or empty, then all the nodes will be /// returned. /// </param> /// <returns>The patch nodes for the mod.</returns> public static ConfigNodePatch[] GetPatches(string modName) { var patchConfigs = GameDatabase.Instance.GetConfigs("COMPATIBILITY"); var patches = new List <ConfigNodePatch>(); foreach (var patchConfig in patchConfigs) { try { var patch = ConfigNodePatch.MakeFromConfig(patchConfig); if (string.IsNullOrEmpty(modName) || patch.modName == modName) { patches.Add(patch); } } catch (Exception ex) { DebugEx.Error("Skipping bad patch node: {0}", ex.Message); continue; } } return(patches.ToArray()); }
/// <summary>Tests if the patch can be applied to the part node.</summary> /// <param name="partNode">The part node to test against.</param> /// <param name="patch">The patch to test.</param> /// <param name="loadContext">The context in which the part is being patched.</param> /// <param name="quietMode">Tells if anything should be reported to the logs.</param> /// <returns><c>true</c> if the TEST rules of the patch have matched.</returns> public static bool TestPatch(ConfigNode partNode, ConfigNodePatch patch, LoadContext loadContext, bool quietMode = false) { ArgumentGuard.NotNull(patch, "patch", context: patch); ArgumentGuard.NotNull(partNode, "partNode", context: patch); ArgumentGuard.OneOf(loadContext, "loadContext", new[] { LoadContext.SFS, LoadContext.Craft }, context: patch); if (partNode.name == "$DELETED" || patch.loadContext != LoadContext.Any && patch.loadContext != loadContext) { return(false); // This node doesn't need patching. } // Check if the part definition matches. var partName = GetPartNameFromUpgradeNode(partNode, loadContext); Preconditions.NotNullOrEmpty(partName, message: "part config node", context: partNode); var patchName = patch.testSection.partTests.GetValue("name"); Preconditions.NotNullOrEmpty(patchName, message: "TEST/PART/name", context: patch); if (patchName != partName) { return(false); // Not for this part. } if (patch.verboseLogging && !quietMode) { DebugEx.Warning("Testing conditions: patch={0}, part={1}", patch, partName); } if (!CheckPatchValues(patch.testSection.partTests, partNode)) { if (patch.verboseLogging && !quietMode) { DebugEx.Warning( "PART test rules haven't matched: patch={0}\nTest node:\n{1}\nPartNode:\n{2}", patch, patch.testSection.partTests, partNode); } return(false); } // Check if the part modules definition matches. This one is tricky. foreach (var moduleTests in patch.testSection.moduleTests) { var modulePattern = moduleTests.GetValue("name"); Preconditions.NotNullOrEmpty(modulePattern, context: moduleTests); var targetModuleNode = LookupModule(partNode, modulePattern); if (targetModuleNode == null) { if (patch.verboseLogging && !quietMode) { DebugEx.Warning( "MODULE cannot be found: patch={0}, moduleName={1}\nPartNode:\n{2}", patch, modulePattern, partNode); } return(false); } if (!CheckPatchValues(moduleTests, targetModuleNode)) { if (patch.verboseLogging && !quietMode) { DebugEx.Warning( "MODULE test rules haven't matched: patch={0}\nTest node:\n{1}\nModeleNode:\n{2}", patch, moduleTests, targetModuleNode); } return(false); } } if (patch.verboseLogging && !quietMode) { DebugEx.Warning("Patch matched the part: patch={0}, part={1}", patch, partName); } return(true); }