Example #1
0
        /// <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);
            }
        }
Example #2
0
        /// <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);
        }
Example #3
0
        /// <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());
        }
Example #4
0
        /// <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);
        }