[
  {
    "path": ".gitignore",
    "content": "# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)\n[Oo]bj/\n\n# mstest test results\nTestResults\n\n## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user*\n*.sln.docstates\n\n# Build results\n[Dd]ebug/\n[Rr]elease/\nx64/\n*_i.c\n*_p.c\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.log\n*.vspscc\n*.vssscc\n.builds\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opensdf\n*.sdf\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*\n\n# NCrunch\n*.ncrunch*\n.*crunch*.local.xml\n\n# Installshield output folder\n[Ee]xpress\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish\n\n# Publish Web Output\n*.Publish.xml\n\n# NuGet Packages Directory\npackages\n\n# Windows Azure Build Output\ncsx\n*.build.csdef\n\n# Windows Store app package directory\nAppPackages/\n\n# Others\n[Bb]in\n[Oo]bj\nsql\nTestResults\n[Tt]est[Rr]esult*\n*.Cache\nClientBin\n[Ss]tyle[Cc]op.*\n~$*\n*.dbmdl\nGenerated_Code #added for RIA/Silverlight projects\n\n# Backup & report files from converting an old project file to a newer\n# Visual Studio version. Backup files are not needed, because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\n\nModuleManager.csproj.user\n\nModuleManager.*.dll\n\n.vs*\n"
  },
  {
    "path": "CopyLocalFalse.txt",
    "content": "﻿Copy Local has been set to false for all dependencies.\nRemark by Jan Van der Haegen: This file has been added because Nuget would installs packages to the solution instead of the project if there are only PowerShell scripts... (So yes: it's safe to remove this file!)"
  },
  {
    "path": "Makefile",
    "content": "# Makefile for building ModuleManager\n\nKSPDIR  := ${HOME}/.local/share/Steam/SteamApps/common/Kerbal\\ Space\\ Program\nMANAGED := KSP_Data/Managed/\n\nINCLUDEFILES := $(wildcard *.cs) \\\n\t$(wildcard Properties/*.cs)\n\nRESGEN2 := resgen2\nGMCS    := /usr/bin/gmcs\nGIT     := /usr/bin/git\nTAR     := /usr/bin/tar\nZIP     := /usr/bin/zip\n\nall: build\n\ninfo:\n\t@echo \"== ModuleManager Build Information ==\"\n\t@echo \"  gmcs:    ${GMCS}\"\n\t@echo \"  git:     ${GIT}\"\n\t@echo \"  tar:     ${TAR}\"\n\t@echo \"  zip:     ${ZIP}\"\n\t@echo \"  KSP Data: ${KSPDIR}\"\n\t@echo \"=====================================\"\n\nbuild: info\n\tmkdir -p build\n\t${RESGEN2} -usesourcepath Properties/Resources.resx build/Resources.resources\n\t${GMCS} -t:library -lib:${KSPDIR}/${MANAGED} \\\n\t\t-r:Assembly-CSharp,Assembly-CSharp-firstpass,UnityEngine \\\n\t\t-out:build/ModuleManager.dll \\\n\t\t-resource:build/Resources.resources,ModuleManager.Properties.Resources.resources \\\n\t\t${INCLUDEFILES}\n\ntar.gz: build\n\t${TAR} zcf ModuleManager-0.$(shell ${GIT} rev-list --count HEAD).g$(shell ${GIT} log -1 --format=\"%h\").tar.gz build/ModuleManager.dll\n\nzip: build\n\t${ZIP} -9 -r ModuleManager-0.$(shell ${GIT} rev-list --count HEAD).g$(shell ${GIT} log -1 --format=\"%h\").zip build/ModuleManager.dll\n\nclean:\n\t@echo \"Cleaning up build and package directories...\"\n\trm -rf build/ package/\n\ninstall: build\n\tmkdir -p ${KSPDIR}/GameData/\n\tcp build/ModuleManager.dll ${KSPDIR}/GameData/\n\nuninstall: info\n\trm -f ${KSPDIR}/GameData/ModuleManager.dll\n\n\n.PHONY : all info build tar.gz zip clean install uninstall\n"
  },
  {
    "path": "ModuleManager/Cats/CatAnimator.cs",
    "content": "﻿using System.Collections;\nusing System.Diagnostics.CodeAnalysis;\nusing UnityEngine;\n\nnamespace ModuleManager.Cats\n{\n    class CatAnimator : MonoBehaviour\n    {\n\n        public Sprite[] frames;\n        public float secFrame = 0.07f;\n\n        private SpriteRenderer spriteRenderer;\n        private int spriteIdx;\n\n        [SuppressMessage(\"CodeQuality\", \"IDE0051\", Justification = \"Called by Unity\")]\n        void Start()\n        {\n            spriteRenderer = GetComponent<SpriteRenderer>();\n            spriteRenderer.sortingOrder = 3;\n            StartCoroutine(Animate());\n        }\n\n\n        IEnumerator Animate()\n        {\n            if (frames.Length == 0)\n                yield return null;\n\n            WaitForSeconds yield = new WaitForSeconds(secFrame);\n\n            while (true)\n            {\n                spriteIdx = (spriteIdx + 1) % frames.Length;\n                spriteRenderer.sprite = frames[spriteIdx];\n                yield return yield;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Cats/CatManager.cs",
    "content": "﻿using System;\nusing UnityEngine;\n\nnamespace ModuleManager.Cats\n{\n    public static class CatManager\n    {\n        private static Sprite[] catFrames;\n        private static Texture2D rainbow;\n        private static int scale = 1;\n\n        public static void LaunchCat()\n        {\n            InitCats();\n\n            GameObject cat = LaunchCat(scale);\n            cat.AddComponent<CatMover>();\n        }\n\n        public static void LaunchCats()\n        {\n            InitCats();\n\n            GameObject catSun = LaunchCat(scale);\n            CatOrbiter catSunOrbiter = catSun.AddComponent<CatOrbiter>();\n            catSunOrbiter.Init(null, 0);\n\n            int cats = UnityEngine.Random.Range(6, 10);\n            for (int i = 0; i < cats; i++)\n            {\n                GameObject cat = LaunchCat(scale);\n                CatOrbiter catOrbiter = cat.AddComponent<CatOrbiter>();\n                catOrbiter.Init(catSunOrbiter, Screen.height * 0.5f);\n\n                int moons = UnityEngine.Random.Range(0, 4);\n\n                for (int j = 0; j < moons; j++)\n                {\n                    GameObject catMoon = LaunchCat(scale);\n                    CatOrbiter catMoonOrbiter = catMoon.AddComponent<CatOrbiter>();\n                    catMoonOrbiter.Init(catOrbiter, Screen.height * 0.06f);\n                }\n            }\n        }\n\n        private static void InitCats()\n        {\n            Texture2D[] tex = new Texture2D[12];\n            for (int i = 0; i < tex.Length; i++)\n            {\n                tex[i] = new Texture2D(70, 42, TextureFormat.ARGB32, false);\n            }\n            tex[0].LoadImage(Properties.Resources.cat1);\n            tex[1].LoadImage(Properties.Resources.cat2);\n            tex[2].LoadImage(Properties.Resources.cat3);\n            tex[3].LoadImage(Properties.Resources.cat4);\n            tex[4].LoadImage(Properties.Resources.cat5);\n            tex[5].LoadImage(Properties.Resources.cat6);\n            tex[6].LoadImage(Properties.Resources.cat7);\n            tex[7].LoadImage(Properties.Resources.cat8);\n            tex[8].LoadImage(Properties.Resources.cat9);\n            tex[9].LoadImage(Properties.Resources.cat10);\n            tex[10].LoadImage(Properties.Resources.cat11);\n            tex[11].LoadImage(Properties.Resources.cat12);\n\n            rainbow = new Texture2D(39, 36, TextureFormat.ARGB32, false);\n            rainbow.LoadImage(Properties.Resources.rainbow);\n            rainbow.Apply();\n\n            catFrames = new Sprite[12];\n\n            for (int i = 0; i < tex.Length; i++)\n            {\n                tex[i].Apply();\n                catFrames[i] = Sprite.Create(tex[i], new Rect(0, 0, tex[i].width, tex[i].height), new Vector2(.5f, .5f));\n                catFrames[i].name = \"cat\" + i;\n            }\n\n            scale = 1;\n            if (Screen.height >= 1080)\n                scale *= 2;\n            if (Screen.height > 1440)\n                scale *= 3;\n\n            Physics2D.gravity = Vector2.zero;\n        }\n\n        private static GameObject LaunchCat(int scale)\n        {\n            GameObject cat = new GameObject(\"NyanCat\");\n            SpriteRenderer sr = cat.AddComponent<SpriteRenderer>();\n            TrailRenderer trail = cat.AddComponent<TrailRenderer>();\n            CatAnimator catAnimator = cat.AddComponent<CatAnimator>();\n\n            sr.sprite = catFrames[0];\n\n            trail.material = new Material(Shader.Find(\"Legacy Shaders/Particles/Alpha Blended\"));\n\n            Debug.Log(\"material = \" + trail.material);\n            trail.material.mainTexture = rainbow;\n            trail.time = 1.5f;\n            trail.startWidth = 0.6f * scale * rainbow.height;\n\n            trail.endColor = Color.white.A(0.1f);\n            trail.colorGradient = new Gradient {alphaKeys = new GradientAlphaKey[3] { new GradientAlphaKey(1, 0), new GradientAlphaKey(1, 0.75f), new GradientAlphaKey(0.2f, 1)  }};\n            trail.Clear();\n            cat.layer = LayerMask.NameToLayer(\"UI\");\n\n            catAnimator.frames = catFrames;\n\n            cat.transform.localScale = 70 * scale * Vector3.one;\n            return cat;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Cats/CatMover.cs",
    "content": "﻿using System.Diagnostics.CodeAnalysis;\nusing UnityEngine;\n\nnamespace ModuleManager.Cats\n{\n    public class CatMover : MonoBehaviour\n    {\n        public Vector3 spos;\n\n        public float vel = 5;\n        private float offsetY;\n\n        public TrailRenderer trail;\n        private SpriteRenderer spriteRenderer;\n\n        private int totalLenth = 100;\n        private float activePos = 0;\n\n        public float scale = 2;\n\n        private const float time = 5;\n        private const float trailTime = time / 4;\n\n        private bool clearTrail = false;\n\n        // Use this for initialization\n        [SuppressMessage(\"CodeQuality\", \"IDE0051\", Justification = \"Called by Unity\")]\n        void Start()\n        {\n            trail = GetComponent<TrailRenderer>();\n            trail.sortingOrder = 2;\n\n            spriteRenderer = GetComponent<SpriteRenderer>();\n\n            offsetY = Mathf.FloorToInt(0.2f * Screen.height);\n\n            spos.z = -1;\n\n            totalLenth = (int) (Screen.width / time * trail.time) + 150;\n            trail.time = trailTime;\n            trail.widthCurve = new AnimationCurve(new Keyframe(0, trail.startWidth ), new Keyframe(0.7f, trail.startWidth), new Keyframe(1, trail.startWidth * 0.9f));\n            clearTrail = true;\n        }\n\n        [SuppressMessage(\"CodeQuality\", \"IDE0051\", Justification = \"Called by Unity\")]\n        void Update()\n        {\n            if (trail.time <= 0f)\n            {\n                trail.time = trailTime;\n            }\n\n            activePos += ((Screen.width / time) * Time.deltaTime);\n\n            if (activePos > (Screen.width + totalLenth))\n            {\n                activePos = -spriteRenderer.sprite.rect.width;\n                clearTrail = true;\n            }\n\n            float f = 2f * Mathf.PI * (activePos) / (Screen.width * 0.5f);\n\n            float heightOffset = Mathf.Sin(f) * (spriteRenderer.sprite.rect.height * scale);\n\n            spos.x = activePos;\n            spos.y = offsetY + heightOffset;\n            \n            transform.position = KSP.UI.UIMainCamera.Camera.ScreenToWorldPoint(spos);\n            transform.rotation = Quaternion.Euler(0, 0, Mathf.Cos(f) * 0.25f * Mathf.PI * Mathf.Rad2Deg);\n\n            if (clearTrail)\n            {\n                trail.Clear();\n                clearTrail = false;\n            }\n\n        }\n\n        \n    }\n}\n"
  },
  {
    "path": "ModuleManager/Cats/CatOrbiter.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\nusing KSP.UI;\nusing UnityEngine;\nusing Random = UnityEngine.Random;\n\nnamespace ModuleManager.Cats\n{\n    class CatOrbiter : MonoBehaviour\n    {\n        private static readonly List<CatOrbiter> orbiters = new List<CatOrbiter>();\n\n        private static CatOrbiter sun;\n\n        private double _mass;\n        public Rigidbody2D rb;\n\n        private Vector2d pos;\n        private Vector2d vel;\n        private Vector2d force;\n        private float scale = 1;\n\n        private const double G = 6.67408E-11;\n\n        public double Mass\n        {\n            get { return _mass; }\n            set\n            {\n                _mass = value;\n                if (rb!=null)\n                    rb.mass = (float)_mass;\n            }\n        }\n        \n        public void Init(CatOrbiter parent, float soi)\n        {\n\n            TimingManager.FixedUpdateAdd(TimingManager.TimingStage.Earlyish, DoForces);\n\n            orbiters.Add(this);\n            rb = gameObject.AddComponent<Rigidbody2D>();\n            rb.isKinematic = true;\n\n            if (orbiters.Count == 1)\n            {\n                sun = this;\n                Vector3 spos = new Vector3(Screen.width * 0.5f, Screen.height * 0.5f, -1);\n                transform.position = KSP.UI.UIMainCamera.Camera.ScreenToWorldPoint(spos);\n                Mass = 2E17;\n                pos.x = transform.position.x;\n                pos.y = transform.position.y;\n            }\n            else\n            {\n                Vector2 relativePos = Random.insideUnitCircle;\n                if (relativePos.magnitude < 0.2)\n                    relativePos = relativePos.normalized * 0.3f;\n                Vector3 spos = UIMainCamera.Camera.WorldToScreenPoint(parent.transform.position) + (Vector3)(relativePos * soi);\n                spos.z = -1;\n                transform.position = UIMainCamera.Camera.ScreenToWorldPoint(spos);\n\n                pos.x = transform.position.x;\n                pos.y = transform.position.y;\n                \n                //int scaleRange = 10;\n                //\n                //float factor = (1 + (scaleRange - 1) * Random.value);\n                //\n                //scale = parent.scale * factor / scaleRange;\n                scale = parent.scale * 0.6f;\n\n                transform.localScale *= scale;\n                TrailRenderer trail = gameObject.GetComponent<TrailRenderer>();\n                trail.colorGradient = new Gradient() {alphaKeys = new GradientAlphaKey[3] { new GradientAlphaKey(1, 0), new GradientAlphaKey(1, 0.7f), new GradientAlphaKey(0, 1)  }};\n                trail.startWidth *= scale;\n                //trail.endWidth *= scale;\n                trail.widthCurve = new AnimationCurve(new Keyframe(0, trail.startWidth ), new Keyframe(0.7f, trail.startWidth), new Keyframe(1, trail.startWidth * 0.9f));\n\n                //Mass = factor * 2E16;\n\n                Mass = parent.Mass * 0.025;\n\n                Vector2d dist = parent.pos - pos;\n                double circularVel = Math.Sqrt(G * (Mass + parent.Mass) / dist.magnitude);\n                if (parent == sun)\n                    circularVel *= Random.Range(0.9f, 1.1f);\n                Debug.Log(\"CatOrbiter \" + circularVel.ToString(\"F3\") + \" \" + Mass.ToString(\"F2\") + \" \" + orbiters[0].Mass.ToString(\"F2\") + \" \" +\n                          dist.magnitude.ToString(\"F2\"));\n\n                Vector3d normal = (Random.value >= 0.3) ? Vector3d.back : Vector3d.forward;\n\n                Vector3d vel3d = Vector3d.Cross(dist, normal).normalized * circularVel;\n                vel.x = parent.vel.x + vel3d.x;\n                vel.y = parent.vel.y + vel3d.y;\n            }\n\n            rb.MovePosition(new Vector2((float)pos.x, (float)pos.y));\n        }\n\n        private void DoForces()\n        {\n            force = Vector2d.zero;\n            foreach (CatOrbiter cat in orbiters)\n            {\n                if (cat == this)\n                    continue;\n\n                // F = G * (m1 * m2) / r^2\n                Vector2d dir = cat.pos - pos;\n                double f = G * (cat.Mass * Mass) / (dir.sqrMagnitude + 10); // +10 to avoid div/0\n                force += (float)f * dir.normalized;\n            }\n        }\n\n        [SuppressMessage(\"CodeQuality\", \"IDE0051\", Justification = \"Called by Unity\")]\n        void OnDestroy()\n        {\n            orbiters.Remove(this);\n            TimingManager.FixedUpdateRemove(TimingManager.TimingStage.Earlyish, DoForces);\n        }\n\n        [SuppressMessage(\"CodeQuality\", \"IDE0051\", Justification = \"Called by Unity\")]\n        void FixedUpdate()\n        {\n            //if (this == sun)\n            //    return;\n\n            vel += Time.fixedDeltaTime * force / Mass;\n            pos += Time.fixedDeltaTime * vel;\n\n            rb.MovePosition(new Vector2((float)pos.x, (float)pos.y));\n\n            double angle = Math.Atan2(vel.y, vel.x) * Mathf.Rad2Deg;\n            rb.MoveRotation((float)angle);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Collections/ArrayEnumerator.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\n\nnamespace ModuleManager.Collections\n{\n    public struct ArrayEnumerator<T> : IEnumerator<T>\n    {\n        private readonly T[] array;\n        private readonly int startIndex;\n        private readonly int length;\n\n        private int index;\n\n        public ArrayEnumerator(params T[] array) : this(array, 0) { }\n\n        public ArrayEnumerator(T[] array, int startIndex) : this(array, startIndex, (array?.Length ?? -1) - startIndex) { }\n\n        public ArrayEnumerator(T[] array, int startIndex, int length)\n        {\n            this.array = array ?? throw new ArgumentNullException(nameof(array));\n\n            if (startIndex < 0)\n                throw new ArgumentException($\"must be non-negative (got {startIndex})\", nameof(startIndex));\n            if (startIndex > array.Length)\n                throw new ArgumentException(\n                    $\"must be less than or equal to array length (array length {array.Length}, startIndex {startIndex})\",\n                    nameof(startIndex)\n                );\n            if (length < 0)\n                throw new ArgumentException($\"must be non-negative (got {length})\", nameof(length));\n            if (startIndex + length > array.Length)\n                throw new ArgumentException(\n                    $\"must fit within the string (array length {array.Length}, startIndex {startIndex}, length {length})\",\n                    nameof(length)\n                );\n\n            this.startIndex = startIndex;\n            this.length = length;\n            index = startIndex - 1;\n        }\n\n        public T Current => array[index];\n        object IEnumerator.Current => Current;\n\n        public void Dispose() { }\n\n        public bool MoveNext()\n        {\n            index++;\n            return index < startIndex + length;\n        }\n\n        public void Reset()\n        {\n            index = startIndex - 1;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Collections/ImmutableStack.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\n\nnamespace ModuleManager.Collections\n{\n    public class ImmutableStack<T> : IEnumerable<T>\n    {\n        public struct Enumerator : IEnumerator<T>\n        {\n            private readonly ImmutableStack<T> head;\n            private ImmutableStack<T> currentStack;\n\n            public Enumerator(ImmutableStack<T> stack)\n            {\n                head = stack;\n                currentStack = null;\n            }\n\n            public T Current => currentStack.value;\n            object IEnumerator.Current => Current;\n\n            public void Dispose() { }\n\n            public bool MoveNext()\n            {\n                if (currentStack == null)\n                {\n                    currentStack = head;\n                    return true;\n                }\n                else if (!currentStack.IsRoot)\n                {\n                    currentStack = currentStack.parent;\n                    return true;\n                }\n                else\n                {\n                    return false;\n                }\n            }\n\n            public void Reset() => currentStack = null;\n        }\n\n        public readonly T value;\n        public readonly ImmutableStack<T> parent;\n\n        public ImmutableStack(T value)\n        {\n            this.value = value;\n        }\n\n        private ImmutableStack(T value, ImmutableStack<T> parent)\n        {\n            this.value = value;\n            this.parent = parent;\n        }\n\n        public bool IsRoot => parent == null;\n        public ImmutableStack<T> Root => IsRoot? this : parent.Root;\n\n        public int Depth => IsRoot ? 1 : parent.Depth + 1;\n\n        public ImmutableStack<T> Push(T newValue)\n        {\n            return new ImmutableStack<T>(newValue, this);\n        }\n\n        public ImmutableStack<T> Pop()\n        {\n            if (IsRoot) throw new InvalidOperationException(\"Cannot pop from the root of a stack\");\n            return parent;\n        }\n\n        public ImmutableStack<T> ReplaceValue(T newValue) => new ImmutableStack<T>(newValue, parent);\n\n        public Enumerator GetEnumerator() => new Enumerator(this);\n        IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();\n        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Collections/KeyValueCache.cs",
    "content": "using System;\nusing System.Collections.Generic;\n\nnamespace ModuleManager.Collections\n{\n    public class KeyValueCache<TKey, TValue>\n    {\n        private readonly Dictionary<TKey, TValue> dict = new Dictionary<TKey, TValue>();\n        private readonly object lockObject = new object();\n\n        public TValue Fetch(TKey key, Func<TValue> createValue)\n        {\n            if (createValue == null) throw new ArgumentNullException(nameof(createValue));\n            lock(lockObject)\n            {\n                if (dict.TryGetValue(key, out TValue value))\n                {\n                    return value;\n                }\n                else\n                {\n                    TValue newValue = createValue();\n                    dict.Add(key, newValue);\n                    return newValue;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Collections/MessageQueue.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\n\nnamespace ModuleManager.Collections\n{\n    public interface IMessageQueue<T> : IEnumerable<T>\n    {\n        void Add(T value);\n        IMessageQueue<T> TakeAll();\n    }\n\n    public class MessageQueue<T> : IMessageQueue<T>, IEnumerable<T>\n    {\n        public sealed class Enumerator : IEnumerator<T>\n        {\n            private readonly MessageQueue<T> queue;\n            private Node current;\n\n            public Enumerator(MessageQueue<T> queue)\n            {\n                this.queue = queue;\n            }\n\n            public T Current => current.value;\n            object IEnumerator.Current => Current;\n\n            public void Dispose() { }\n\n            public bool MoveNext()\n            {\n                if (current == null)\n                    current = queue.head;\n                else\n                    current = current.next;\n\n                return current != null;\n            }\n\n            public void Reset()\n            {\n                current = null;\n            }\n        }\n\n        private class Node\n        {\n            public Node next;\n            public readonly T value;\n\n            public Node(T value)\n            {\n                this.value = value;\n            }\n        }\n\n        private readonly object lockObject = new object();\n        private Node head;\n        private Node tail;\n\n        public void Add(T value)\n        {\n            Node node = new Node(value);\n            lock (lockObject)\n            {\n                if (head == null)\n                {\n                    head = node;\n                    tail = node;\n                }\n                else\n                {\n                    tail.next = node;\n                    tail = node;\n                }\n            }\n        }\n\n        public IMessageQueue<T> TakeAll()\n        {\n            MessageQueue<T> queue = new MessageQueue<T>();\n            lock(lockObject)\n            {\n                queue.head = head;\n                queue.tail = tail;\n                head = null;\n                tail = null;\n            }\n            return queue;\n        }\n\n        public Enumerator GetEnumerator() => new Enumerator(this);\n        IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();\n        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Command.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager\n{\n    public enum Command\n    {\n        Insert,\n\n        Delete,\n\n        Edit,\n\n        Replace,\n\n        Copy,\n\n        Rename,\n\n        Paste,\n\n        Special,\n\n        Create\n    }\n}\n"
  },
  {
    "path": "ModuleManager/CommandParser.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager\n{\n    public static class CommandParser\n    {\n        public static Command Parse(string name, out string valueName)\n        {\n            if (name.Length == 0)\n            {\n                valueName = string.Empty;\n                return Command.Insert;\n            }\n            Command ret;\n            switch (name[0])\n            {\n                case '@':\n                    ret = Command.Edit;\n                    break;\n\n                case '%':\n                    ret = Command.Replace;\n                    break;\n\n                case '-':\n                case '!':\n                    ret = Command.Delete;\n                    break;\n\n                case '+':\n                case '$':\n                    ret = Command.Copy;\n                    break;\n\n                case '|':\n                    ret = Command.Rename;\n                    break;\n\n                case '#':\n                    ret = Command.Paste;\n                    break;\n\n                case '*':\n                    ret = Command.Special;\n                    break;\n\n                case '&':\n                    ret = Command.Create;\n                    break;\n\n                default:\n                    valueName = name;\n                    return Command.Insert;\n            }\n            valueName = name.Substring(1);\n            return ret;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/CustomConfigsManager.cs",
    "content": "﻿using System;\nusing System.IO;\nusing UnityEngine;\n\nusing static ModuleManager.FilePathRepository;\n\nnamespace ModuleManager\n{\n    [KSPAddon(KSPAddon.Startup.SpaceCentre, false)]\n    public class CustomConfigsManager : MonoBehaviour\n    {\n        internal void Start()\n        {\n            if (HighLogic.CurrentGame.Parameters.Career.TechTreeUrl != techTreeFile && File.Exists(techTreePath))\n            {\n                Log(\"Setting modded tech tree as the active one\");\n                HighLogic.CurrentGame.Parameters.Career.TechTreeUrl = techTreeFile;\n            }\n        }\n\n        public static void Log(String s)\n        {\n            print(\"[CustomConfigsManager] \" + s);\n        }\n\n    }\n}\n"
  },
  {
    "path": "ModuleManager/ExceptionIntercept/InterceptLogHandler.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Reflection;\nusing UnityEngine;\nusing Object = UnityEngine.Object;\n\nnamespace ModuleManager.UnityLogHandle\n{\n    class InterceptLogHandler : ILogHandler\n    {\n        private readonly ILogHandler baseLogHandler;\n        private readonly List<Assembly> brokenAssemblies = new List<Assembly>();\n        private readonly int gamePathLength;\n\n        public static string Warnings { get; private set; } = \"\";\n\n        public InterceptLogHandler(ILogHandler baseLogHandler)\n        {\n            this.baseLogHandler = baseLogHandler ?? throw new ArgumentNullException(nameof(baseLogHandler));\n            gamePathLength = Path.GetFullPath(KSPUtil.ApplicationRootPath).Length;\n        }\n\n        public void LogFormat(LogType logType, Object context, string format, params object[] args)\n        {\n            baseLogHandler.LogFormat(logType, context, format, args);\n        }\n\n        public void LogException(Exception exception, Object context)\n        {\n            baseLogHandler.LogException(exception, context);\n\n            if (exception is ReflectionTypeLoadException ex)\n            {\n                string message = \"Intercepted a ReflectionTypeLoadException. List of broken DLLs:\\n\";\n                try\n                {\n                    var assemblies = ex.Types.Where(x => x != null).Select(x => x.Assembly).Distinct();\n                    foreach (Assembly assembly in assemblies)\n                    {\n                        if (Warnings == \"\")\n                        {\n                            Warnings = \"Mod(s) DLL that are not compatible with this version of KSP\\n\";\n                        }\n                        string modInfo = assembly.GetName().Name + \" \" + assembly.GetName().Version + \" \" +\n                                         assembly.Location.Remove(0, gamePathLength) + \"\\n\";\n                        if (!brokenAssemblies.Contains(assembly))\n                        {\n                            brokenAssemblies.Add(assembly);\n                            Warnings += modInfo;\n                        }\n                        message += modInfo;\n                    }\n                }\n                catch (Exception e)\n                {\n                    message += \"Exception \" + e.GetType().Name + \" while handling the exception...\";\n                }\n                ModuleManager.Log(message);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Extensions/ByteArrayExtensions.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager.Extensions\n{\n    public static class ByteArrayExtensions\n    {\n        public static string ToHex(this byte[] data)\n        {\n            if (data == null) throw new ArgumentNullException(nameof(data));\n            char[] result = new char[data.Length * 2];\n\n            for (int i = 0; i < data.Length; i++)\n            {\n                result[i * 2] = GetHexValue(data[i] / 16);\n                result[i * 2 + 1] = GetHexValue(data[i] % 16);\n            }\n\n            return new string(result);\n        }\n\n        private static char GetHexValue(int i)\n        {\n            if (i < 10)\n                return (char)(i + '0');\n            else\n                return (char)(i - 10 + 'a');\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Extensions/ConfigNodeExtensions.cs",
    "content": "﻿using System;\nusing System.Text;\n\nnamespace ModuleManager.Extensions\n{\n    public static class ConfigNodeExtensions\n    {\n        public static void ShallowCopyFrom(this ConfigNode toNode, ConfigNode fromeNode)\n        {\n            toNode.ClearData();\n            foreach (ConfigNode.Value value in fromeNode.values)\n                toNode.values.Add(value);\n            foreach (ConfigNode node in fromeNode.nodes)\n                toNode.nodes.Add(node);\n        }\n\n        // KSP implementation of ConfigNode.CreateCopy breaks with badly formed nodes (nodes with a blank name)\n        public static ConfigNode DeepCopy(this ConfigNode from)\n        {\n            ConfigNode to = new ConfigNode(from.name);\n            foreach (ConfigNode.Value value in from.values)\n                to.AddValueSafe(value.name, value.value);\n            foreach (ConfigNode node in from.nodes)\n            {\n                ConfigNode newNode = DeepCopy(node);\n                to.nodes.Add(newNode);\n            }\n            return to;\n        }\n\n        public static void PrettyPrint(this ConfigNode node, ref StringBuilder sb, string indent)\n        {\n            if (sb == null) throw new ArgumentNullException(nameof(sb));\n            if (indent == null) indent = string.Empty;\n            if (node == null)\n            {\n                sb.Append(indent + \"<null node>\\n\");\n                return;\n            }\n            sb.AppendFormat(\"{0}{1}\\n{2}{{\\n\", indent, node.name ?? \"<null>\", indent);\n            string newindent = indent + \"  \";\n            if (node.values == null)\n            {\n                sb.AppendFormat(\"{0}<null value list>\\n\", newindent);\n            }\n            else\n            {\n                foreach (ConfigNode.Value value in node.values)\n                {\n                    if (value == null)\n                        sb.AppendFormat(\"{0}<null value>\\n\", newindent);\n                    else\n                        sb.AppendFormat(\"{0}{1} = {2}\\n\", newindent, value.name ?? \"<null>\", value.value ?? \"<null>\");\n                }\n            }\n\n            if (node.nodes == null)\n            {\n                sb.AppendFormat(\"{0}<null node list>\\n\", newindent);\n            }\n            else\n            {\n                foreach (ConfigNode subnode in node.nodes)\n                {\n                    subnode.PrettyPrint(ref sb, newindent);\n                }\n            }\n\n            sb.AppendFormat(\"{0}}}\\n\", indent);\n        }\n\n        public static void AddValueSafe(this ConfigNode node, string name, string value)\n        {\n            node.values.Add(new ConfigNode.Value(name, value));\n        }\n\n        public static void EscapeValuesRecursive(this ConfigNode theNode)\n        {\n            foreach (ConfigNode subNode in theNode.nodes)\n            {\n                subNode.EscapeValuesRecursive();\n            }\n\n            foreach (ConfigNode.Value value in theNode.values)\n            {\n                value.value = value.value.Replace(\"\\n\", \"\\\\n\");\n                value.value = value.value.Replace(\"\\r\", \"\\\\r\");\n                value.value = value.value.Replace(\"\\t\", \"\\\\t\");\n            }\n        }\n\n        public static void UnescapeValuesRecursive(this ConfigNode theNode)\n        {\n            foreach (ConfigNode subNode in theNode.nodes)\n            {\n                subNode.UnescapeValuesRecursive();\n            }\n\n            foreach (ConfigNode.Value value in theNode.values)\n            {\n                value.value = value.value.Replace(\"\\\\n\", \"\\n\");\n                value.value = value.value.Replace(\"\\\\r\", \"\\r\");\n                value.value = value.value.Replace(\"\\\\t\", \"\\t\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Extensions/IBasicLoggerExtensions.cs",
    "content": "﻿using System;\nusing UnityEngine;\nusing ModuleManager.Logging;\n\nnamespace ModuleManager.Extensions\n{\n    public static class IBasicLoggerExtensions\n    {\n        public static void Info(this IBasicLogger logger, string message) => logger.Log(new LogMessage(LogType.Log, message));\n        public static void Warning(this IBasicLogger logger, string message) => logger.Log(new LogMessage(LogType.Warning, message));\n        public static void Error(this IBasicLogger logger, string message) => logger.Log(new LogMessage(LogType.Error, message));\n\n        public static void Exception(this IBasicLogger logger, Exception exception)\n        {\n            if (exception == null) throw new ArgumentNullException(nameof(exception));\n            logger.Log(new LogMessage(LogType.Exception, exception.ToString()));\n        }\n\n        public static void Exception(this IBasicLogger logger, string message, Exception exception)\n        {\n            if (message == null) throw new ArgumentNullException(nameof(message));\n            if (exception == null) throw new ArgumentNullException(nameof(exception));\n            logger.Log(new LogMessage(LogType.Exception, message + \": \" + exception.ToString()));\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Extensions/NodeStackExtensions.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Text;\nusing NodeStack = ModuleManager.Collections.ImmutableStack<ConfigNode>;\n\nnamespace ModuleManager.Extensions\n{\n    public static class NodeStackExtensions\n    {\n        public static string GetPath(this NodeStack stack)\n        {\n            int length = stack.Sum(node => node.name.Length) + stack.Depth - 1;\n            StringBuilder sb = new StringBuilder(length);\n\n            foreach (ConfigNode node in stack)\n            {\n                string nodeName = node.name;\n                sb.Insert(0, node.name);\n                if (sb.Length < sb.Capacity) sb.Insert(0, '/');\n            }\n\n            return sb.ToString();\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Extensions/StringExtensions.cs",
    "content": "﻿using System;\nusing System.Text.RegularExpressions;\n\nnamespace ModuleManager.Extensions\n{\n    public static class StringExtensions\n    {\n        public static bool IsBracketBalanced(this string s)\n        {\n            int level = 0;\n            foreach (char c in s)\n            {\n                if (c == '[') level++;\n                else if (c == ']') level--;\n\n                if (level < 0) return false;\n            }\n            return level == 0;\n        }\n\n        private static readonly Regex whitespaceRegex = new Regex(@\"\\s+\");\n\n        public static string RemoveWS(this string withWhite)\n        {\n            return whitespaceRegex.Replace(withWhite, \"\");\n        }\n\n        public static bool Contains(this string str, string value, out int index)\n        {\n            if (str == null) throw new ArgumentNullException(nameof(str));\n            if (value == null) throw new ArgumentNullException(nameof(value));\n\n            index = str.IndexOf(value, StringComparison.CurrentCultureIgnoreCase);\n            return index != -1;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Extensions/UrlConfigExtensions.cs",
    "content": "﻿using System;\nusing System.Text;\n\nnamespace ModuleManager.Extensions\n{\n    public static class UrlConfigExtensions\n    {\n        public static string SafeUrl(this UrlDir.UrlConfig url)\n        {\n            if (url == null) return \"<null>\";\n\n            string nodeName;\n\n            if (!string.IsNullOrEmpty(url.type?.Trim()))\n            {\n                nodeName = url.type;\n            }\n            else if (url.type == null)\n            {\n                nodeName = \"<null>\";\n            }\n            else\n            {\n                nodeName = \"<blank>\";\n            }\n\n            string parentUrl = null;\n\n            if (url.parent != null)\n            {\n                try\n                {\n                    parentUrl = url.parent.url;\n                }\n                catch\n                {\n                    parentUrl = \"<unknown>\";\n                }\n            }\n\n            if (parentUrl == null)\n                return nodeName;\n            else\n                return parentUrl + \"/\" + nodeName;\n        }\n\n        public static string PrettyPrint(this UrlDir.UrlConfig config)\n        {\n            if (config == null) return \"<null UrlConfig>\";\n\n            StringBuilder sb = new StringBuilder();\n\n            sb.Append(config.SafeUrl());\n            sb.Append('\\n');\n            config.config.PrettyPrint(ref sb, \"  \");\n            \n            return sb.ToString();\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Extensions/UrlDirExtensions.cs",
    "content": "﻿using System;\nusing System.Linq;\n\nnamespace ModuleManager.Extensions\n{\n    public static class UrlDirExtensions\n    {\n        public static UrlDir.UrlFile Find(this UrlDir urlDir, string url)\n        {\n            if (urlDir == null) throw new ArgumentNullException(nameof(urlDir));\n            if (url == null) throw new ArgumentNullException(nameof(url));\n            string[] splits = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);\n\n            UrlDir currentDir = urlDir;\n\n            for (int i = 0; i < splits.Length - 1; i++)\n            {\n                currentDir = currentDir.children.FirstOrDefault(subDir => subDir.name == splits[i]);\n                if (currentDir == null) return null;\n            }\n            \n            string fileName = splits[splits.Length - 1];\n            string fileExtension = null;\n\n            int idx = fileName.LastIndexOf('.');\n\n            if (idx > -1)\n            {\n                fileExtension = fileName.Substring(idx + 1);\n                fileName = fileName.Substring(0, idx);\n            }\n\n            foreach (UrlDir.UrlFile file in currentDir.files)\n            {\n                if (file.name != fileName) continue;\n                if (fileExtension != null && fileExtension != file.fileExtension) continue;\n                return file;\n            }\n\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Extensions/UrlFileExtensions.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager.Extensions\n{\n    public static class UrlFileExtensions\n    {\n        public static string GetUrlWithExtension(this UrlDir.UrlFile urlFile)\n        {\n            return $\"{urlFile.url}.{urlFile.fileExtension}\";\n        }\n        public static string GetNameWithExtension(this UrlDir.UrlFile urlFile)\n        {\n            return $\"{urlFile.name}.{urlFile.fileExtension}\";\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/FatalErrorHandler.cs",
    "content": "﻿using System;\nusing UnityEngine;\n\nnamespace ModuleManager\n{\n    public static class FatalErrorHandler\n    {\n        public static void HandleFatalError(string message)\n        {\n            try\n            {\n                PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f),\n                    new Vector2(0.5f, 0.5f),\n                    new MultiOptionDialog(\n                        \"ModuleManagerFatalError\",\n                        $\"ModuleManager has encountered a fatal error and KSP needs to close.\\n\\n{message}\\n\\nPlease see KSP's log for addtional details\",\n                        \"ModuleManager - Fatal Error\",\n                        HighLogic.UISkin,\n                        new Rect(0.5f, 0.5f, 500f, 60f),\n                        new DialogGUIFlexibleSpace(),\n                        new DialogGUIHorizontalLayout(\n                            new DialogGUIFlexibleSpace(),\n                            new DialogGUIButton(\"Quit\", Application.Quit, 140.0f, 30.0f, true),\n                            new DialogGUIFlexibleSpace()\n                        )\n                    ),\n                    true,\n                    HighLogic.UISkin);\n            }\n            catch(Exception ex)\n            {\n                Debug.LogError(\"Exception while trying to create the fatal exception dialog\");\n                Debug.LogException(ex);\n                Application.Quit();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/FilePathRepository.cs",
    "content": "﻿using System;\nusing System.IO;\n\nnamespace ModuleManager\n{\n    internal static class FilePathRepository\n    {\n        internal static readonly string normalizedRootPath = Path.GetFullPath(KSPUtil.ApplicationRootPath);\n        internal static readonly string cachePath = Path.Combine(normalizedRootPath, \"GameData\", \"ModuleManager.ConfigCache\");\n\n        internal static readonly string techTreeFile = Path.Combine(\"GameData\", \"ModuleManager.TechTree\");\n        internal static readonly string techTreePath = Path.Combine(normalizedRootPath, techTreeFile);\n\n        internal static readonly string physicsFile = Path.Combine(\"GameData\", \"ModuleManager.Physics\");\n        internal static readonly string physicsPath = Path.Combine(normalizedRootPath, physicsFile);\n        internal static readonly string defaultPhysicsPath = Path.Combine(normalizedRootPath, \"Physics.cfg\");\n\n        internal static readonly string partDatabasePath = Path.Combine(normalizedRootPath, \"PartDatabase.cfg\");\n\n        internal static readonly string shaPath = Path.Combine(normalizedRootPath, \"GameData\", \"ModuleManager.ConfigSHA\");\n\n        internal static readonly string logsDirPath = Path.Combine(normalizedRootPath, \"Logs\", \"ModuleManager\");\n        internal static readonly string logPath = Path.Combine(logsDirPath, \"ModuleManager.log\");\n        internal static readonly string patchLogPath = Path.Combine(logsDirPath, \"MMPatch.log\");\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Fix16.cs",
    "content": "﻿using System.Collections;\nusing System.Diagnostics.CodeAnalysis;\n\nnamespace ModuleManager\n{\n    class Fix16 : LoadingSystem\n    {\n        [SuppressMessage(\"CodeQuality\", \"IDE0051\", Justification = \"Called by Unity\")]\n        private void Awake()\n        {\n            if (Instance != null)\n            {\n                DestroyImmediate(this);\n                return;\n            }\n\n            Instance = this;\n            DontDestroyOnLoad(gameObject);\n        }\n\n        private static Fix16 Instance { get; set; }\n\n        private bool ready;\n\n        private int count;\n\n        private int current;\n\n        private const int yieldStep = 20;\n\n        public override bool IsReady()\n        {\n            return ready;\n        }\n\n        public override string ProgressTitle()\n        {\n            return \"Fix 1.6.0 \" + current + \"/\" + count;\n        }\n\n        public override float ProgressFraction()\n        {\n            return (float) current / count;\n        }\n\n        public override void StartLoad()\n        {\n            ready = false;\n            \n            count = PartLoader.LoadedPartsList.Count;\n\n            StartCoroutine(DoFix());\n        }\n\n        private IEnumerator DoFix()\n        {\n            int yieldCounter = 0;\n            for (current = 0; current < count; current++)\n            {\n                AvailablePart avp = PartLoader.LoadedPartsList[current];\n                if (avp.partPrefab.dragModel == Part.DragModel.CUBE && !avp.partPrefab.DragCubes.Procedural &&\n                    !avp.partPrefab.DragCubes.None && avp.partPrefab.DragCubes.Cubes.Count == 0)\n                {\n                    DragCubeSystem.Instance.LoadDragCubes(avp.partPrefab);\n                }\n\n                if (yieldCounter++ >= yieldStep)\n                {\n                    yieldCounter = 0;\n                    yield return null;\n                }\n            }\n\n            ready = true;\n            yield return null;\n        }\n\n        public override float LoadWeight()\n        {\n            return 0.1f;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Logging/IBasicLogger.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager.Logging\n{\n    // Stripped down version of UnityEngine.ILogger\n    public interface IBasicLogger\n    {\n        void Log(ILogMessage message);\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Logging/ILogMessage.cs",
    "content": "﻿using System;\nusing UnityEngine;\n\nnamespace ModuleManager.Logging\n{\n    public interface ILogMessage\n    {\n        LogType LogType { get; }\n        DateTime Timestamp { get; }\n        string Message { get; }\n        string ToLogString();\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Logging/LogMessage.cs",
    "content": "﻿using System;\nusing UnityEngine;\n\nnamespace ModuleManager.Logging\n{\n    public class LogMessage : ILogMessage\n    {\n        private const string DATETIME_FORMAT_STRING = \"HH:mm:ss.fff\";\n\n        public LogType LogType { get; }\n        public DateTime Timestamp { get; }\n        public string Message { get; }\n\n        public LogMessage(LogType logType, string message)\n        {\n            LogType = logType;\n            Timestamp = DateTime.Now;\n            Message = message ?? throw new ArgumentNullException(nameof(message));\n        }\n\n        public LogMessage(ILogMessage logMessage, string newMessage)\n        {\n            if (logMessage == null) throw new ArgumentNullException(nameof(logMessage));\n            LogType = logMessage.LogType;\n            Timestamp = logMessage.Timestamp;\n            Message = newMessage ?? throw new ArgumentNullException(nameof(newMessage));\n        }\n\n        public string ToLogString()\n        {\n            string prefix;\n            if (LogType == LogType.Log)\n                prefix = \"LOG\";\n            else if (LogType == LogType.Warning)\n                prefix = \"WRN\";\n            else if (LogType == LogType.Error)\n                prefix = \"ERR\";\n            else if (LogType == LogType.Assert)\n                prefix = \"AST\";\n            else if (LogType == LogType.Exception)\n                prefix = \"EXC\";\n            else\n                prefix = \"???\";\n            \n            return $\"[{prefix} {Timestamp.ToString(DATETIME_FORMAT_STRING)}] {Message}\";\n        }\n\n        public override string ToString()\n        {\n            return $\"[{GetType().FullName} LogType={LogType} Message={Message}]\";\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Logging/LogSplitter.cs",
    "content": "using System;\n\nnamespace ModuleManager.Logging\n{\n    public class LogSplitter : IBasicLogger\n    {\n        private readonly IBasicLogger logger1;\n        private readonly IBasicLogger logger2;\n\n        public LogSplitter(IBasicLogger logger1, IBasicLogger logger2)\n        {\n            this.logger1 = logger1 ?? throw new ArgumentNullException(nameof(logger1));\n            this.logger2 = logger2 ?? throw new ArgumentNullException(nameof(logger2));\n        }\n\n        public void Log(ILogMessage message)\n        {\n            if (message == null) throw new ArgumentNullException(nameof(message));\n            logger1.Log(message);\n            logger2.Log(message);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Logging/PrefixLogger.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager.Logging\n{\n    public class PrefixLogger : IBasicLogger\n    {\n        private readonly string prefix;\n        private readonly IBasicLogger logger;\n\n        public PrefixLogger(string prefix, IBasicLogger logger)\n        {\n            if (string.IsNullOrEmpty(prefix)) throw new ArgumentNullException(nameof(prefix));\n            this.prefix = $\"[{prefix}] \";\n            this.logger = logger ?? throw new ArgumentNullException(nameof(logger));\n        }\n\n        public void Log(ILogMessage message)\n        {\n            if (message == null) throw new ArgumentNullException(nameof(message));\n            logger.Log(new LogMessage(message, prefix + message.Message));\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Logging/QueueLogRunner.cs",
    "content": "using System;\nusing System.IO;\n\nusing ModuleManager.Collections;\n\nnamespace ModuleManager.Logging\n{\n    public class QueueLogRunner\n    {\n        private enum State\n        {\n            Initialized,\n            Running,\n            StopRequested,\n            Stopped,\n        }\n\n        private State state = State.Initialized;\n        private readonly IMessageQueue<ILogMessage> logQueue;\n        private readonly long timeToWaitForLogsMs;\n\n        public QueueLogRunner(IMessageQueue<ILogMessage> logQueue, long timeToWaitForLogsMs = 50)\n        {\n            this.logQueue = logQueue ?? throw new ArgumentNullException(nameof(logQueue));\n            if (timeToWaitForLogsMs < 0) throw new ArgumentException(\"must be non-negative\", nameof(timeToWaitForLogsMs));\n            this.timeToWaitForLogsMs = timeToWaitForLogsMs;\n        }\n\n        public void RequestStop()\n        {\n            if (state == State.StopRequested || state == State.Stopped) return;\n            if (state != State.Running) throw new InvalidOperationException($\"Cannot request stop from {state} state\");\n            state = State.StopRequested;\n        }\n\n        public void Run(IBasicLogger logger)\n        {\n            if (state != State.Initialized) throw new InvalidOperationException($\"Cannot run from {state} state\");\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n            state = State.Running;\n\n            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();\n\n            while (state == State.Running)\n            {\n                stopwatch.Start();\n\n                foreach (ILogMessage message in logQueue.TakeAll())\n                {\n                    logger.Log(message);\n                }\n\n                long timeRemaining = timeToWaitForLogsMs - stopwatch.ElapsedMilliseconds;\n                if (timeRemaining > 0)\n                    System.Threading.Thread.Sleep((int)timeRemaining);\n\n                stopwatch.Reset();\n            }\n\n            foreach (ILogMessage message in logQueue.TakeAll())\n            {\n                logger.Log(message);\n            }\n\n            state = State.Stopped;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Logging/QueueLogger.cs",
    "content": "﻿using System;\nusing ModuleManager.Collections;\n\nnamespace ModuleManager.Logging\n{\n    public class QueueLogger : IBasicLogger\n    {\n        private readonly IMessageQueue<ILogMessage> queue;\n\n        public QueueLogger(IMessageQueue<ILogMessage> queue)\n        {\n            this.queue = queue ?? throw new ArgumentNullException(nameof(queue));\n        }\n\n        public void Log(ILogMessage message)\n        {\n            if (message == null) throw new ArgumentNullException(nameof(message));\n            queue.Add(message);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Logging/StreamLogger.cs",
    "content": "﻿using System;\nusing System.IO;\n\nnamespace ModuleManager.Logging\n{\n    public sealed class StreamLogger : IBasicLogger, IDisposable\n    {\n        private readonly StreamWriter streamWriter;\n        private bool disposed = false;\n\n        public StreamLogger(Stream stream)\n        {\n            streamWriter = new StreamWriter(stream);\n        }\n\n        public void Log(ILogMessage message)\n        {\n            if (disposed) throw new InvalidOperationException(\"Object has already been disposed\");\n            if (message == null) throw new ArgumentNullException(nameof(message));\n\n            streamWriter.WriteLine(message.ToLogString());\n        }\n\n        public void Dispose()\n        {\n            // Flushes and closes the StreamWriter and the underlying stream\n            streamWriter.Close();\n\n            disposed = true;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Logging/UnityLogger.cs",
    "content": "﻿using System;\nusing UnityEngine;\n\nnamespace ModuleManager.Logging\n{\n    public class UnityLogger : IBasicLogger\n    {\n        private readonly ILogger logger;\n\n        public UnityLogger(ILogger logger)\n        {\n            this.logger = logger ?? throw new ArgumentNullException(nameof(logger));\n        }\n\n        public void Log(ILogMessage message)\n        {\n            if (message == null) throw new ArgumentNullException(nameof(message));\n            logger.Log(message.LogType, message.Message);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/MMPatchLoader.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Globalization;\nusing System.IO;\nusing System.Linq;\nusing System.Reflection;\nusing System.Text;\nusing System.Text.RegularExpressions;\n\nusing ModuleManager.Collections;\nusing ModuleManager.Logging;\nusing ModuleManager.Extensions;\nusing ModuleManager.Threading;\nusing ModuleManager.Tags;\nusing ModuleManager.Patches;\nusing ModuleManager.Progress;\nusing NodeStack = ModuleManager.Collections.ImmutableStack<ConfigNode>;\n\nusing static ModuleManager.FilePathRepository;\n\nnamespace ModuleManager\n{\n    public class MMPatchLoader\n    {\n        private const string PHYSICS_NODE_NAME = \"PHYSICSGLOBALS\";\n        private const string TECH_TREE_NODE_NAME = \"TechTree\";\n\n        public string status = \"\";\n\n        public string errors = \"\";\n\n        public static bool keepPartDB = false;\n\n        private static readonly KeyValueCache<string, Regex> regexCache = new KeyValueCache<string, Regex>();\n\n        private string configSha;\n        private readonly Dictionary<string, string> filesSha = new Dictionary<string, string>();\n\n        private const int STATUS_UPDATE_INVERVAL_MS = 33;\n\n        private readonly IEnumerable<ModListGenerator.ModAddedByAssembly> modsAddedByAssemblies;\n        private readonly IBasicLogger logger;\n\n        public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback)\n        {\n            PostPatchLoader.AddPostPatchCallback(callback);\n        }\n\n        public MMPatchLoader(IEnumerable<ModListGenerator.ModAddedByAssembly> modsAddedByAssemblies, IBasicLogger logger)\n        {\n            this.modsAddedByAssemblies = modsAddedByAssemblies ?? throw new ArgumentNullException(nameof(modsAddedByAssemblies));\n            this.logger = logger ?? throw new ArgumentNullException(nameof(logger));\n        }\n\n        public IEnumerable<IProtoUrlConfig> Run()\n        {\n            Stopwatch patchSw = new Stopwatch();\n            patchSw.Start();\n\n            status = \"Checking Cache\";\n            logger.Info(status);\n\n            bool useCache = false;\n            try\n            {\n                useCache = IsCacheUpToDate();\n            }\n            catch (Exception ex)\n            {\n                logger.Exception(\"Exception in IsCacheUpToDate\", ex);\n            }\n\n#if DEBUG\n            //useCache = false;\n#endif\n\n            IEnumerable<IProtoUrlConfig> databaseConfigs = null;\n\n            if (!useCache)\n            {\n                if (!Directory.Exists(logsDirPath)) Directory.CreateDirectory(logsDirPath);\n                MessageQueue<ILogMessage> patchLogQueue = new MessageQueue<ILogMessage>();\n                QueueLogRunner logRunner = new QueueLogRunner(patchLogQueue);\n                ITaskStatus loggingThreadStatus = BackgroundTask.Start(delegate\n                {\n                    using StreamLogger streamLogger = new StreamLogger(new FileStream(patchLogPath, FileMode.Create));\n                    streamLogger.Info(\"Log started at \" + DateTime.Now.ToString(\"yyyy-MM-dd HH:mm:ss.fff\"));\n                    logRunner.Run(streamLogger);\n                    streamLogger.Info(\"Done!\");\n                });\n                IBasicLogger patchLogger = new LogSplitter(logger, new QueueLogger(patchLogQueue));\n\n                IPatchProgress progress = new PatchProgress(patchLogger);\n                status = \"Pre patch init\";\n                patchLogger.Info(status);\n                IEnumerable<string> mods = ModListGenerator.GenerateModList(modsAddedByAssemblies, progress, patchLogger);\n\n                // If we don't use the cache then it is best to clean the PartDatabase.cfg\n                if (!keepPartDB && File.Exists(partDatabasePath))\n                    File.Delete(partDatabasePath);\n\n                LoadPhysicsConfig();\n\n                #region Sorting Patches\n\n                status = \"Extracting patches\";\n                patchLogger.Info(status);\n\n                UrlDir gameData = GameDatabase.Instance.root.children.First(dir => dir.type == UrlDir.DirectoryType.GameData && dir.name == \"\");\n                INeedsChecker needsChecker = new NeedsChecker(mods, gameData, progress, patchLogger);\n                ITagListParser tagListParser = new TagListParser(progress);\n                IProtoPatchBuilder protoPatchBuilder = new ProtoPatchBuilder(progress);\n                IPatchCompiler patchCompiler = new PatchCompiler();\n                PatchExtractor extractor = new PatchExtractor(progress, patchLogger, needsChecker, tagListParser, protoPatchBuilder, patchCompiler);\n\n                // Have to convert to an array because we will be removing patches\n                IEnumerable<IPatch> extractedPatches =\n                    GameDatabase.Instance.root.AllConfigs.Select(urlConfig => extractor.ExtractPatch(urlConfig)).Where(patch => patch != null);\n                PatchList patchList = new PatchList(mods, extractedPatches, progress);\n\n                #endregion\n\n                #region Applying patches\n\n                status = \"Applying patches\";\n                patchLogger.Info(status);\n\n                IPass currentPass = null;\n\n                progress.OnPassStarted.Add(delegate (IPass pass)\n                {\n                    currentPass = pass;\n                    StatusUpdate(progress, currentPass.Name);\n                });\n\n                System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();\n                stopwatch.Start();\n\n                progress.OnPatchApplied.Add(delegate\n                {\n                    long timeRemaining = STATUS_UPDATE_INVERVAL_MS - stopwatch.ElapsedMilliseconds;\n                    if (timeRemaining < 0)\n                    {\n                        StatusUpdate(progress, currentPass.Name);\n                        stopwatch.Reset();\n                        stopwatch.Start();\n                    }\n                });\n\n                PatchApplier applier = new PatchApplier(progress, patchLogger);\n                databaseConfigs = applier.ApplyPatches(patchList);\n\n                stopwatch.Stop();\n                StatusUpdate(progress);\n\n                patchLogger.Info(\"Done patching\");\n\n                #endregion Applying patches\n\n                #region Saving Cache\n\n                foreach (KeyValuePair<string, int> item in progress.Counter.warningFiles)\n                {\n                    patchLogger.Warning(item.Value + \" warning\" + (item.Value > 1 ? \"s\" : \"\") + \" related to GameData/\" + item.Key);\n                }\n\n                if (progress.Counter.errors > 0 || progress.Counter.exceptions > 0)\n                {\n                    foreach (KeyValuePair<string, int> item in progress.Counter.errorFiles)\n                    {\n                        errors += item.Value + \" error\" + (item.Value > 1 ? \"s\" : \"\") + \" related to GameData/\" + item.Key\n                                  + \"\\n\";\n                    }\n\n                    patchLogger.Warning(\"Errors in patch prevents the creation of the cache\");\n                    try\n                    {\n                        if (File.Exists(cachePath))\n                            File.Delete(cachePath);\n                        if (File.Exists(shaPath))\n                            File.Delete(shaPath);\n                    }\n                    catch (Exception e)\n                    {\n                        patchLogger.Exception(\"Exception while deleting stale cache \", e);\n                    }\n                }\n                else\n                {\n                    status = \"Saving Cache\";\n                    patchLogger.Info(status);\n                    CreateCache(databaseConfigs, progress.Counter.patchedNodes);\n                }\n\n                StatusUpdate(progress);\n\n                #endregion Saving Cache\n\n                SaveModdedTechTree(databaseConfigs);\n                SaveModdedPhysics(databaseConfigs);\n\n                logRunner.RequestStop();\n\n                while (loggingThreadStatus.IsRunning)\n                {\n                    System.Threading.Thread.Sleep(100);\n                }\n\n                if (loggingThreadStatus.IsExitedWithError)\n                {\n                    logger.Error(\"The patching thread threw an exception\");\n                    throw loggingThreadStatus.Exception;\n                }\n            }\n            else\n            {\n                status = \"Loading from Cache\";\n                logger.Info(status);\n                databaseConfigs = LoadCache();\n\n                if (File.Exists(patchLogPath))\n                {\n                    logger.Info(\"Dumping patch log\");\n                    logger.Info(\"\\n#### BEGIN PATCH LOG ####\\n\\n\\n\" + File.ReadAllText(patchLogPath) + \"\\n\\n\\n#### END PATCH LOG ####\");\n                }\n                else\n                {\n                    logger.Error(\"Patch log does not exist: \" + patchLogPath);\n                }\n            }\n\n            if (KSP.Localization.Localizer.Instance != null)\n                KSP.Localization.Localizer.SwitchToLanguage(KSP.Localization.Localizer.CurrentLanguage);\n\n            logger.Info(status + \"\\n\" + errors);\n\n            patchSw.Stop();\n            logger.Info(\"Ran in \" + ((float)patchSw.ElapsedMilliseconds / 1000).ToString(\"F3\") + \"s\");\n\n            return databaseConfigs;\n        }\n\n        private void LoadPhysicsConfig()\n        {\n            logger.Info(\"Loading Physics.cfg\");\n            UrlDir gameDataDir = GameDatabase.Instance.root.AllDirectories.First(d => d.path.EndsWith(\"GameData\") && d.name == \"\" && d.url == \"\");\n            // need to use a file with a cfg extension to get the right fileType or you can't AddConfig on it\n            UrlDir.UrlFile physicsUrlFile = new UrlDir.UrlFile(gameDataDir, new FileInfo(defaultPhysicsPath));\n            // Since it loaded the default config badly (sub node only) we clear it first\n            physicsUrlFile.configs.Clear();\n            // And reload it properly\n            ConfigNode physicsContent = ConfigNode.Load(defaultPhysicsPath);\n            physicsContent.name = PHYSICS_NODE_NAME;\n            physicsUrlFile.AddConfig(physicsContent);\n            gameDataDir.files.Add(physicsUrlFile);\n        }\n\n        private void SaveModdedPhysics(IEnumerable<IProtoUrlConfig> databaseConfigs)\n        {\n            IEnumerable<IProtoUrlConfig> configs = databaseConfigs.Where(config => config.NodeType == PHYSICS_NODE_NAME);\n            int count = configs.Count();\n\n            if (count == 0)\n            {\n                logger.Info($\"No {PHYSICS_NODE_NAME} node found. No custom Physics config will be saved\");\n                return;\n            }\n\n            if (count > 1)\n            {\n                logger.Info($\"{count} {PHYSICS_NODE_NAME} nodes found. A patch may be wrong. Using the first one\");\n            }\n\n            configs.First().Node.Save(physicsPath);\n        }\n\n        private bool IsCacheUpToDate()\n        {\n            Stopwatch sw = new Stopwatch();\n            sw.Start();\n\n            using System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create();\n            using System.Security.Cryptography.SHA256 filesha = System.Security.Cryptography.SHA256.Create();\n            UrlDir.UrlFile[] files = GameDatabase.Instance.root.AllConfigFiles.ToArray();\n\n            filesSha.Clear();\n\n            for (int i = 0; i < files.Length; i++)\n            {\n                string url = files[i].GetUrlWithExtension();\n                // Hash the file path so the checksum change if files are moved\n                byte[] pathBytes = Encoding.UTF8.GetBytes(url);\n                sha.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0);\n\n                // hash the file content\n                byte[] contentBytes = File.ReadAllBytes(files[i].fullPath);\n                sha.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0);\n\n                filesha.ComputeHash(contentBytes);\n                if (!filesSha.ContainsKey(url))\n                {\n                    filesSha.Add(url, BitConverter.ToString(filesha.Hash));\n                }\n                else\n                {\n                    logger.Warning(\"Duplicate fileSha key. This should not append. The key is \" + url);\n                }\n            }\n\n            // Hash the mods dll path so the checksum change if dlls are moved or removed (impact NEEDS)\n            foreach (AssemblyLoader.LoadedAssembly dll in AssemblyLoader.loadedAssemblies)\n            {\n                string path = dll.url + \"/\" + dll.name;\n                byte[] pathBytes = Encoding.UTF8.GetBytes(path);\n                sha.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0);\n            }\n\n            foreach (ModListGenerator.ModAddedByAssembly mod in modsAddedByAssemblies)\n            {\n                byte[] modBytes = Encoding.UTF8.GetBytes(mod.modName);\n                sha.TransformBlock(modBytes, 0, modBytes.Length, modBytes, 0);\n            }\n\n            byte[] godsFinalMessageToHisCreation = Encoding.UTF8.GetBytes(\"We apologize for the inconvenience.\");\n            sha.TransformFinalBlock(godsFinalMessageToHisCreation, 0, godsFinalMessageToHisCreation.Length);\n\n            configSha = BitConverter.ToString(sha.Hash);\n            sha.Clear();\n            filesha.Clear();\n\n            sw.Stop();\n\n            logger.Info(\"SHA generated in \" + ((float)sw.ElapsedMilliseconds / 1000).ToString(\"F3\") + \"s\");\n            logger.Info(\"      SHA = \" + configSha);\n\n            bool useCache = false;\n            if (File.Exists(shaPath))\n            {\n                ConfigNode shaConfigNode = ConfigNode.Load(shaPath);\n                if (shaConfigNode != null && shaConfigNode.HasValue(\"SHA\") && shaConfigNode.HasValue(\"version\") && shaConfigNode.HasValue(\"KSPVersion\"))\n                {\n                    string storedSHA = shaConfigNode.GetValue(\"SHA\");\n                    string version = shaConfigNode.GetValue(\"version\");\n                    string kspVersion = shaConfigNode.GetValue(\"KSPVersion\");\n                    ConfigNode filesShaNode = shaConfigNode.GetNode(\"FilesSHA\");\n                    useCache = CheckFilesChange(files, filesShaNode);\n                    useCache = useCache && storedSHA.Equals(configSha);\n                    useCache = useCache && version.Equals(Assembly.GetExecutingAssembly().GetName().Version.ToString());\n                    useCache = useCache && kspVersion.Equals(Versioning.version_major + \".\" + Versioning.version_minor + \".\" + Versioning.Revision + \".\" + Versioning.BuildID);\n                    useCache = useCache && File.Exists(cachePath);\n                    useCache = useCache && File.Exists(physicsPath);\n                    useCache = useCache && File.Exists(techTreePath);\n                    logger.Info(\"Cache SHA = \" + storedSHA);\n                    logger.Info(\"useCache = \" + useCache);\n                }\n            }\n            return useCache;\n        }\n\n        private bool CheckFilesChange(UrlDir.UrlFile[] files, ConfigNode shaConfigNode)\n        {\n            bool noChange = true;\n            StringBuilder changes = new StringBuilder();\n\n            changes.Append(\"Changes :\\n\");\n\n            for (int i = 0; i < files.Length; i++)\n            {\n                string url = files[i].GetUrlWithExtension();\n                ConfigNode fileNode = GetFileNode(shaConfigNode, url);\n                string fileSha = fileNode?.GetValue(\"SHA\");\n\n                if (fileNode == null)\n                    continue;\n\n                if (fileSha == null || filesSha[url] != fileSha)\n                {\n                    changes.Append(\"Changed : \" + fileNode.GetValue(\"filename\") + \".cfg\\n\");\n                    noChange = false;\n                }\n            }\n            for (int i = 0; i < files.Length; i++)\n            {\n                string url = files[i].GetUrlWithExtension();\n                ConfigNode fileNode = GetFileNode(shaConfigNode, url);\n\n                if (fileNode == null)\n                {\n                    changes.Append(\"Added   : \" + url + \"\\n\");\n                    noChange = false;\n                }\n                shaConfigNode.RemoveNode(fileNode);\n            }\n            foreach (ConfigNode fileNode in shaConfigNode.GetNodes())\n            {\n                changes.Append(\"Deleted : \" + fileNode.GetValue(\"filename\") + \"\\n\");\n                noChange = false;\n            }\n            if (!noChange)\n                logger.Info(changes.ToString());\n            return noChange;\n        }\n\n        private ConfigNode GetFileNode(ConfigNode shaConfigNode, string filename)\n        {\n            for (int i = 0; i < shaConfigNode.nodes.Count; i++)\n            {\n                ConfigNode file = shaConfigNode.nodes[i];\n                if (file.name == \"FILE\" && file.GetValue(\"filename\") == filename)\n                    return file;\n            }\n            return null;\n        }\n\n\n        private void CreateCache(IEnumerable<IProtoUrlConfig> databaseConfigs, int patchedNodeCount)\n        {\n            ConfigNode shaConfigNode = new ConfigNode();\n            shaConfigNode.AddValue(\"SHA\", configSha);\n            shaConfigNode.AddValue(\"version\", Assembly.GetExecutingAssembly().GetName().Version.ToString());\n            shaConfigNode.AddValue(\"KSPVersion\", Versioning.version_major + \".\" + Versioning.version_minor + \".\" + Versioning.Revision + \".\" + Versioning.BuildID);\n            ConfigNode filesSHANode = shaConfigNode.AddNode(\"FilesSHA\");\n\n            ConfigNode cache = new ConfigNode();\n\n            cache.AddValue(\"patchedNodeCount\", patchedNodeCount.ToString());\n\n            foreach (IProtoUrlConfig urlConfig in databaseConfigs)\n            {\n                ConfigNode node = cache.AddNode(\"UrlConfig\");\n                node.AddValue(\"parentUrl\", urlConfig.UrlFile.GetUrlWithExtension());\n\n                ConfigNode urlNode = urlConfig.Node.DeepCopy();\n                urlNode.EscapeValuesRecursive();\n\n                node.AddNode(urlNode);\n            }\n\n            foreach (var file in GameDatabase.Instance.root.AllConfigFiles)\n            {\n                string url = file.GetUrlWithExtension();\n                // \"/Physics\" is the node we created manually to loads the PHYSIC config\n                if (file.url != \"/Physics\" && filesSha.ContainsKey(url))\n                {\n                    ConfigNode shaNode = filesSHANode.AddNode(\"FILE\");\n                    shaNode.AddValue(\"filename\", url);\n                    shaNode.AddValue(\"SHA\", filesSha[url]);\n                    filesSha.Remove(url);\n                }\n            }\n\n            logger.Info(\"Saving cache\");\n\n            try\n            {\n                shaConfigNode.Save(shaPath);\n            }\n            catch (Exception e)\n            {\n                logger.Exception(\"Exception while saving the sha\", e);\n            }\n            try\n            {\n                cache.Save(cachePath);\n                return;\n            }\n            catch (NullReferenceException e)\n            {\n                logger.Exception(\"NullReferenceException while saving the cache\", e);\n            }\n            catch (Exception e)\n            {\n                logger.Exception(\"Exception while saving the cache\", e);\n            }\n\n            try\n            {\n                logger.Error(\"An error occured while creating the cache. Deleting the cache files to avoid keeping a bad cache\");\n                if (File.Exists(cachePath))\n                    File.Delete(cachePath);\n                if (File.Exists(shaPath))\n                    File.Delete(shaPath);\n            }\n            catch (Exception e)\n            {\n                logger.Exception(\"Exception while deleting the cache\", e);\n            }\n        }\n\n        private void SaveModdedTechTree(IEnumerable<IProtoUrlConfig> databaseConfigs)\n        {\n            IEnumerable<IProtoUrlConfig> configs = databaseConfigs.Where(config => config.NodeType == TECH_TREE_NODE_NAME);\n            int count = configs.Count();\n\n            if (count == 0)\n            {\n                logger.Info($\"No {TECH_TREE_NODE_NAME} node found. No custom {TECH_TREE_NODE_NAME} will be saved\");\n                return;\n            }\n\n            if (count > 1)\n            {\n                logger.Info($\"{count} {TECH_TREE_NODE_NAME} nodes found. A patch may be wrong. Using the first one\");\n            }\n\n            ConfigNode techNode = new ConfigNode(TECH_TREE_NODE_NAME);\n            techNode.AddNode(configs.First().Node);\n            techNode.Save(techTreePath);\n        }\n\n        private IEnumerable<IProtoUrlConfig> LoadCache()\n        {\n            ConfigNode cache = ConfigNode.Load(cachePath);\n\n            if (cache.HasValue(\"patchedNodeCount\") && int.TryParse(cache.GetValue(\"patchedNodeCount\"), out int patchedNodeCount))\n                status = \"ModuleManager: \" + patchedNodeCount + \" patch\" + (patchedNodeCount != 1 ? \"es\" : \"\") +  \" loaded from cache\";\n\n            // Create the fake file where we load the physic config cache\n            UrlDir gameDataDir = GameDatabase.Instance.root.AllDirectories.First(d => d.path.EndsWith(\"GameData\") && d.name == \"\" && d.url == \"\");\n            // need to use a file with a cfg extension to get the right fileType or you can't AddConfig on it\n            UrlDir.UrlFile physicsUrlFile = new UrlDir.UrlFile(gameDataDir, new FileInfo(defaultPhysicsPath));\n            gameDataDir.files.Add(physicsUrlFile);\n\n            List<IProtoUrlConfig> databaseConfigs = new List<IProtoUrlConfig>(cache.nodes.Count);\n\n            foreach (ConfigNode node in cache.nodes)\n            {\n                string parentUrl = node.GetValue(\"parentUrl\");\n\n                UrlDir.UrlFile parent = gameDataDir.Find(parentUrl);\n                if (parent != null)\n                {\n                    node.nodes[0].UnescapeValuesRecursive();\n                    databaseConfigs.Add(new ProtoUrlConfig(parent, node.nodes[0]));\n                }\n                else\n                {\n                    logger.Warning(\"Parent null for \" + parentUrl);\n                }\n            }\n            logger.Info(\"Cache Loaded\");\n\n            return databaseConfigs;\n        }\n\n        private void StatusUpdate(IPatchProgress progress, string activity = null)\n        {\n            status = \"ModuleManager: \" + progress.Counter.patchedNodes + \" patch\" + (progress.Counter.patchedNodes != 1 ? \"es\" : \"\") + \" applied\";\n            if (progress.ProgressFraction < 1f - float.Epsilon)\n                status += \" (\" + progress.ProgressFraction.ToString(\"P0\") + \")\";\n\n            if (activity != null)\n                status += \"\\n\" + activity;\n\n            if (progress.Counter.warnings > 0)\n                status += \", found <color=yellow>\" + progress.Counter.warnings + \" warning\" + (progress.Counter.warnings != 1 ? \"s\" : \"\") + \"</color>\";\n\n            if (progress.Counter.errors > 0)\n                status += \", found <color=orange>\" + progress.Counter.errors + \" error\" + (progress.Counter.errors != 1 ? \"s\" : \"\") + \"</color>\";\n\n            if (progress.Counter.exceptions > 0)\n                status += \", encountered <color=red>\" + progress.Counter.exceptions + \" exception\" + (progress.Counter.exceptions != 1 ? \"s\" : \"\") + \"</color>\";\n        }\n\n        #region Applying Patches\n\n        // Name is group 1, index is group 2, vector related filed is group 3, vector separator is group 4, operator is group 5\n        private static readonly Regex parseValue = new Regex(@\"([\\w\\&\\-\\.\\?\\*\\#+/^!\\(\\) ]+(?:,[^*\\d][\\w\\&\\-\\.\\?\\*\\(\\) ]*)*)(?:,(-?[0-9\\*]+))?(?:\\[((?:[0-9\\*]+)+)(?:,(.))?\\])?\");\n\n        // ModifyNode applies the ConfigNode mod as a 'patch' to ConfigNode original, then returns the patched ConfigNode.\n        // it uses FindConfigNodeIn(src, nodeType, nodeName, nodeTag) to recurse.\n        public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext context)\n        {\n            ConfigNode newNode = original.value.DeepCopy();\n            NodeStack nodeStack = original.ReplaceValue(newNode);\n\n            #region Values\n\n            #if LOGSPAM\n            string vals = \"[ModuleManager] modding values\";\n            #endif\n            foreach (ConfigNode.Value modVal in mod.values)\n            {\n                #if LOGSPAM\n                vals += \"\\n   \" + modVal.name + \"= \" + modVal.value;\n                #endif\n\n                Command cmd = CommandParser.Parse(modVal.name, out string valName);\n\n                Operator op;\n                if (valName.Length > 2 && valName[valName.Length - 2] == ',')\n                    op = Operator.Assign;\n                else\n                    op = OperatorParser.Parse(valName, out valName);\n\n                if (cmd == Command.Special)\n                {\n                    ConfigNode.Value val = RecurseVariableSearch(valName, nodeStack.Push(mod), context);\n\n                    if (val == null)\n                    {\n                        context.progress.Error(context.patchUrl, \"Error - Cannot find value assigning command: \" + valName);\n                        continue;\n                    }\n\n                    if (op != Operator.Assign)\n                    {\n                        if (double.TryParse(modVal.value, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double s)\n                            && double.TryParse(val.value, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double os))\n                        {\n                            switch (op)\n                            {\n                                case Operator.Multiply:\n                                    val.value = (os * s).ToString(CultureInfo.InvariantCulture);\n                                    break;\n\n                                case Operator.Divide:\n                                    val.value = (os / s).ToString(CultureInfo.InvariantCulture);\n                                    break;\n\n                                case Operator.Add:\n                                    val.value = (os + s).ToString(CultureInfo.InvariantCulture);\n                                    break;\n\n                                case Operator.Subtract:\n                                    val.value = (os - s).ToString(CultureInfo.InvariantCulture);\n                                    break;\n\n                                case Operator.Exponentiate:\n                                    val.value = Math.Pow(os, s).ToString(CultureInfo.InvariantCulture);\n                                    break;\n                            }\n                        }\n                    }\n                    else\n                    {\n                        val.value = modVal.value;\n                    }\n                    continue;\n                }\n\n                Match match = parseValue.Match(valName);\n                if (!match.Success)\n                {\n                    context.progress.Error(context.patchUrl, \"Error - Cannot parse value modifying command: \" + valName);\n                    continue;\n                }\n\n                // Get the bits and pieces from the regexp\n                valName = match.Groups[1].Value;\n\n                // Get a position for editing a vector\n                int position = 0;\n                bool isPosStar = false;\n                if (match.Groups[3].Success)\n                {\n                    if (match.Groups[3].Value == \"*\")\n                        isPosStar = true;\n                    else if (!int.TryParse(match.Groups[3].Value, out position))\n                    {\n                        context.progress.Error(context.patchUrl, \"Error - Unable to parse number as number. Very odd.\");\n                        continue;\n                    }\n                }\n                char seperator = ',';\n                if (match.Groups[4].Success)\n                {\n                    seperator = match.Groups[4].Value[0];\n                }\n\n                // In this case insert the value at position index (with the same node names)\n                int index = 0;\n                bool isStar = false;\n                if (match.Groups[2].Success)\n                {\n                    if (match.Groups[2].Value == \"*\")\n                        isStar = true;\n                    // can have \"node,n *\" (for *= ect)\n                    else if (!int.TryParse(match.Groups[2].Value, out index))\n                    {\n                        context.progress.Error(context.patchUrl, \"Error - Unable to parse number as number. Very odd.\");\n                        continue;\n                    }\n                }\n\n                int valCount = 0;\n                for (int i=0; i<newNode.CountValues; i++)\n                    if (newNode.values[i].name == valName)\n                        valCount++;\n\n                string varValue;\n                switch (cmd)\n                {\n                    case Command.Insert:\n                        if (match.Groups[5].Success)\n                        {\n                            context.progress.Error(context.patchUrl, \"Error - Cannot use operators with insert value: \" + mod.name);\n                        }\n                        else\n                        {\n                            // Insert at the end by default\n                            varValue = ProcessVariableSearch(modVal.value, nodeStack, context);\n                            if (varValue != null)\n                                InsertValue(newNode, match.Groups[2].Success ? index : int.MaxValue, valName, varValue);\n                            else\n                                context.progress.Error(context.patchUrl, \"Error - Cannot parse variable search when inserting new key \" + valName + \" = \" +\n                                    modVal.value);\n                        }\n                        break;\n\n                    case Command.Replace:\n                        if (match.Groups[2].Success || match.Groups[5].Success || valName.Contains('*')\n                            || valName.Contains('?'))\n                        {\n                            if (match.Groups[2].Success)\n                                context.progress.Error(context.patchUrl, \"Error - Cannot use index with replace (%) value: \" + mod.name);\n                            if (match.Groups[5].Success)\n                                context.progress.Error(context.patchUrl, \"Error - Cannot use operators with replace (%) value: \" + mod.name);\n                            if (valName.Contains('*') || valName.Contains('?'))\n                                context.progress.Error(context.patchUrl, \"Error - Cannot use wildcards (* or ?) with replace (%) value: \" + mod.name);\n                        }\n                        else\n                        {\n                            varValue = ProcessVariableSearch(modVal.value, nodeStack, context);\n                            if (varValue != null)\n                            {\n                                newNode.RemoveValues(valName);\n                                newNode.AddValueSafe(valName, varValue);\n                            }\n                            else\n                            {\n                                context.progress.Error(context.patchUrl, \"Error - Cannot parse variable search when replacing (%) key \" + valName + \" = \" +\n                                    modVal.value);\n                            }\n                        }\n                        break;\n\n                    case Command.Edit:\n                    case Command.Copy:\n\n                        // Format is @key = value or @key *= value or @key += value or @key -= value\n                        // or @key,index = value or @key,index *= value or @key,index += value or @key,index -= value\n\n                        while (index < valCount)\n                        {\n                            varValue = ProcessVariableSearch(modVal.value, nodeStack, context);\n\n                            if (varValue != null)\n                            {\n                                string value = FindAndReplaceValue(\n                                    mod,\n                                    ref valName,\n                                    varValue, newNode,\n                                    op,\n                                    index,\n                                    out ConfigNode.Value origVal,\n                                    context,\n                                    match.Groups[3].Success,\n                                    position,\n                                    isPosStar,\n                                    seperator\n                                );\n\n                                if (value != null)\n                                {\n                                    #if LOGSPAM\n                                    if (origVal.value != value)\n                                        vals += \": \" + origVal.value + \" -> \" + value;\n                                    #endif\n\n                                    if (cmd != Command.Copy)\n                                        origVal.value = value;\n                                    else\n                                        newNode.AddValueSafe(valName, value);\n                                }\n                            }\n                            else\n                            {\n                                context.progress.Error(context.patchUrl, \"Error - Cannot parse variable search when editing key \" + valName + \" = \" + modVal.value);\n                            }\n\n                            if (isStar) index++;\n                            else break;\n                        }\n                        break;\n\n                    case Command.Delete:\n                        if (match.Groups[5].Success)\n                        {\n                            context.progress.Error(context.patchUrl, \"Error - Cannot use operators with delete (- or !) value: \" + mod.name);\n                        }\n                        else if (match.Groups[2].Success)\n                        {\n                            while (index < valCount)\n                            {\n                                // If there is an index, use it.\n                                ConfigNode.Value v = FindValueIn(newNode, valName, index);\n                                if (v != null)\n                                    newNode.values.Remove(v);\n                                if (isStar) index++;\n                                else break;\n                            }\n                        }\n                        else if (valName.Contains('*') || valName.Contains('?'))\n                        {\n                            // Delete all matching wildcard\n                            ConfigNode.Value last = null;\n                            while (true)\n                            {\n                                ConfigNode.Value v = FindValueIn(newNode, valName, index++);\n                                if (v == last)\n                                    break;\n                                last = v;\n                                newNode.values.Remove(v);\n                            }\n                        }\n                        else\n                        {\n                            // Default is to delete ALL values that match. (backwards compatibility)\n                            newNode.RemoveValues(valName);\n                        }\n                        break;\n\n                    case Command.Rename:\n                        if (nodeStack.IsRoot)\n                        {\n                            context.progress.Error(context.patchUrl, \"Error - Renaming nodes does not work on top nodes\");\n                            break;\n                        }\n                        newNode.name = modVal.value;\n                        break;\n\n                    case Command.Create:\n                        if (match.Groups[2].Success || match.Groups[5].Success || valName.Contains('*')\n                            || valName.Contains('?'))\n                        {\n                            if (match.Groups[2].Success)\n                                context.progress.Error(context.patchUrl, \"Error - Cannot use index with create (&) value: \" + mod.name);\n                            if (match.Groups[5].Success)\n                                context.progress.Error(context.patchUrl, \"Error - Cannot use operators with create (&) value: \" + mod.name);\n                            if (valName.Contains('*') || valName.Contains('?'))\n                                context.progress.Error(context.patchUrl, \"Error - Cannot use wildcards (* or ?) with create (&) value: \" + mod.name);\n                        }\n                        else\n                        {\n                            varValue = ProcessVariableSearch(modVal.value, nodeStack, context);\n                            if (varValue != null)\n                            {\n                                if (!newNode.HasValue(valName))\n                                    newNode.AddValueSafe(valName, varValue);\n                            }\n                            else\n                            {\n                                context.progress.Error(context.patchUrl, \"Error - Cannot parse variable search when replacing (&) key \" + valName + \" = \" +\n                                    modVal.value);\n                            }\n                        }\n                        break;\n                }\n            }\n            #if LOGSPAM\n            log(vals);\n            #endif\n\n            #endregion Values\n\n            #region Nodes\n\n            foreach (ConfigNode subMod in mod.nodes)\n            {\n                subMod.name = subMod.name.RemoveWS();\n\n                if (!subMod.name.IsBracketBalanced())\n                {\n                    context.progress.Error(context.patchUrl,\n                        \"Error - Skipping a patch subnode with unbalanced square brackets or a space (replace them with a '?') in \"\n                        + mod.name + \" : \\n\" + subMod.name + \"\\n\");\n                    continue;\n                }\n\n                string subName = subMod.name;\n                Command command = CommandParser.Parse(subName, out string tmp);\n\n                if (command == Command.Insert)\n                {\n                    ConfigNode newSubMod = new ConfigNode(subMod.name);\n                    newSubMod = ModifyNode(nodeStack.Push(newSubMod), subMod, context);\n                    subName = newSubMod.name;\n                    if (subName.Contains(\",\") && int.TryParse(subName.Split(',')[1], out int index))\n                    {\n                        // In this case insert the node at position index (with the same node names)\n                        newSubMod.name = subName.Split(',')[0];\n                        InsertNode(newNode, newSubMod, index);\n                    }\n                    else\n                    {\n                        newNode.AddNode(newSubMod);\n                    }\n                }\n                else if (command == Command.Paste)\n                {\n                    //int start = subName.IndexOf('[');\n                    //int end = subName.LastIndexOf(']');\n                    //if (start == -1 || end == -1 || end - start < 1)\n                    //{\n                    //    log(\"Pasting a node require a [path] to the node to paste\" + mod.name + \" : \\n\" + subMod.name + \"\\n\");\n                    //    errorCount++;\n                    //    continue;\n                    //}\n\n                    //string newName = subName.Substring(0, start);\n                    //string path = subName.Substring(start + 1, end - start - 1);\n\n                    ConfigNode toPaste = RecurseNodeSearch(subName.Substring(1), nodeStack, context);\n\n                    if (toPaste == null)\n                    {\n                        context.progress.Error(context.patchUrl, \"Error - Can not find the node to paste in \" + mod.name + \" : \" + subMod.name + \"\\n\");\n                        continue;\n                    }\n\n                    ConfigNode newSubMod = new ConfigNode(toPaste.name);\n                    newSubMod = ModifyNode(nodeStack.Push(newSubMod), toPaste, context);\n                    if (subName.LastIndexOf(',') > 0 && int.TryParse(subName.Substring(subName.LastIndexOf(',') + 1), out int index))\n                    {\n                        // In this case insert the node at position index\n                        InsertNode(newNode, newSubMod, index);\n                    }\n                    else\n                        newNode.AddNode(newSubMod);\n                }\n                else\n                {\n                    string constraints = \"\";\n                    string tag = \"\";\n                    string nodeType, nodeName;\n                    int index = 0;\n                    #if LOGSPAM\n                    string msg = \"\";\n                    #endif\n                    List<ConfigNode> subNodes = new List<ConfigNode>();\n\n                    // three ways to specify:\n                    // NODE,n will match the nth node (NODE is the same as NODE,0)\n                    // NODE,* will match ALL nodes\n                    // NODE:HAS[condition] will match ALL nodes with condition\n                    if (subName.Contains(\":HAS[\", out int hasStart))\n                    {\n                        constraints = subName.Substring(hasStart + 5, subName.LastIndexOf(']') - hasStart - 5);\n                        subName = subName.Substring(0, hasStart);\n                    }\n\n                    if (subName.Contains(\",\"))\n                    {\n                        tag = subName.Split(',')[1];\n                        subName = subName.Split(',')[0];\n                        int.TryParse(tag, out index);\n                    }\n\n                    if (subName.Contains(\"[\"))\n                    {\n                        // format @NODETYPE[Name] {...}\n                        // or @NODETYPE[Name, index] {...}\n                        nodeType = subName.Substring(1).Split('[')[0];\n                        nodeName = subName.Split('[')[1].Replace(\"]\", \"\");\n                    }\n                    else\n                    {\n                        // format @NODETYPE {...} or ! instead of @\n                        nodeType = subName.Substring(1);\n                        nodeName = null;\n                    }\n\n                    if (tag == \"*\" || constraints.Length > 0)\n                    {\n                        // get ALL nodes\n                        if (command != Command.Replace)\n                        {\n                            ConfigNode n, last = null;\n                            while (true)\n                            {\n                                n = FindConfigNodeIn(newNode, nodeType, nodeName, index++);\n                                if (n == last || n == null)\n                                    break;\n                                if (CheckConstraints(n, constraints))\n                                    subNodes.Add(n);\n                                last = n;\n                            }\n                        }\n#if LOGSPAM\n                        else\n                            msg += \"  cannot wildcard a % node: \" + subMod.name + \"\\n\";\n#endif\n                    }\n                    else\n                    {\n                        // just get one node\n                        ConfigNode n = FindConfigNodeIn(newNode, nodeType, nodeName, index);\n                        if (n != null)\n                            subNodes.Add(n);\n                    }\n\n                    if (command == Command.Replace)\n                    {\n                        // if the original exists modify it\n                        if (subNodes.Count > 0)\n                        {\n                            #if LOGSPAM\n                            msg += \"  Applying subnode \" + subMod.name + \"\\n\";\n                            #endif\n                            ConfigNode newSubNode = ModifyNode(nodeStack.Push(subNodes[0]), subMod, context);\n                            subNodes[0].ShallowCopyFrom(newSubNode);\n                            subNodes[0].name = newSubNode.name;\n                        }\n                        else\n                        {\n                            // if not add the mod node without the % in its name\n                            #if LOGSPAM\n                            msg += \"  Adding subnode \" + subMod.name + \"\\n\";\n                            #endif\n\n                            ConfigNode copy = new ConfigNode(nodeType);\n\n                            if (nodeName != null)\n                                copy.AddValueSafe(\"name\", nodeName);\n\n                            ConfigNode newSubNode = ModifyNode(nodeStack.Push(copy), subMod, context);\n                            newNode.nodes.Add(newSubNode);\n                        }\n                    }\n                    else if (command == Command.Create)\n                    {\n                        if (subNodes.Count == 0)\n                        {\n                            #if LOGSPAM\n                            msg += \"  Adding subnode \" + subMod.name + \"\\n\";\n                            #endif\n\n                            ConfigNode copy = new ConfigNode(nodeType);\n\n                            if (nodeName != null)\n                                copy.AddValueSafe(\"name\", nodeName);\n\n                            ConfigNode newSubNode = ModifyNode(nodeStack.Push(copy), subMod, context);\n                            newNode.nodes.Add(newSubNode);\n                        }\n                    }\n                    else\n                    {\n                        // find each original subnode to modify, modify it and add the modified.\n                        #if LOGSPAM\n                        if (subNodes.Count == 0) // no nodes to modify!\n                            msg += \"  Could not find node(s) to modify: \" + subMod.name + \"\\n\";\n                        #endif\n\n                        foreach (ConfigNode subNode in subNodes)\n                        {\n                            #if LOGSPAM\n                            msg += \"  Applying subnode \" + subMod.name + \"\\n\";\n                            #endif\n                            ConfigNode newSubNode;\n                            switch (command)\n                            {\n                                case Command.Edit:\n\n                                    // Edit in place\n                                    newSubNode = ModifyNode(nodeStack.Push(subNode), subMod, context);\n                                    subNode.ShallowCopyFrom(newSubNode);\n                                    subNode.name = newSubNode.name;\n                                    break;\n\n                                case Command.Delete:\n\n                                    // Delete the node\n                                    newNode.nodes.Remove(subNode);\n                                    break;\n\n                                case Command.Copy:\n\n                                    // Copy the node\n                                    newSubNode = ModifyNode(nodeStack.Push(subNode), subMod, context);\n                                    newNode.nodes.Add(newSubNode);\n                                    break;\n                            }\n                        }\n                    }\n                    #if LOGSPAM\n                    print(msg);\n                    #endif\n                }\n            }\n\n            #endregion Nodes\n\n            return newNode;\n        }\n\n\n        // Search for a ConfigNode by a path alike string\n        private static ConfigNode RecurseNodeSearch(string path, NodeStack nodeStack, PatchContext context)\n        {\n            //log(\"Path : \\\"\" + path + \"\\\"\");\n\n            if (path[0] == '/')\n            {\n                return RecurseNodeSearch(path.Substring(1), nodeStack.Root, context);\n            }\n\n            int nextSep = path.IndexOf('/');\n\n            bool root = (path[0] == '@');\n            int shift = root ? 1 : 0;\n            string subName = (nextSep != -1) ? path.Substring(shift, nextSep - shift) : path.Substring(shift);\n            string nodeType, nodeName;\n            string constraint = \"\";\n\n            int index = 0;\n            if (subName.Contains(\":HAS[\", out int hasStart))\n            {\n                constraint = subName.Substring(hasStart + 5, subName.LastIndexOf(']') - hasStart - 5);\n                subName = subName.Substring(0, hasStart);\n            }\n            else if (subName.Contains(\",\"))\n            {\n                string tag = subName.Split(',')[1];\n                subName = subName.Split(',')[0];\n                int.TryParse(tag, out index);\n            }\n\n            if (subName.Contains(\"[\"))\n            {\n                // NODETYPE[Name]\n                nodeType = subName.Split('[')[0];\n                nodeName = subName.Split('[')[1].Replace(\"]\", \"\");\n            }\n            else\n            {\n                // NODETYPE\n                nodeType = subName;\n                nodeName = null;\n            }\n\n            // ../XXXXX\n            if (path.StartsWith(\"../\"))\n            {\n                if (nodeStack.IsRoot)\n                    return null;\n\n                return RecurseNodeSearch(path.Substring(3), nodeStack.Pop(), context);\n            }\n\n            //log(\"nextSep : \\\"\" + nextSep + \" \\\" root : \\\"\" + root + \" \\\" nodeType : \\\"\" + nodeType + \"\\\" nodeName : \\\"\" + nodeName + \"\\\"\");\n\n            // @XXXXX\n            if (root)\n            {\n                bool foundNodeType = false;\n                foreach (IProtoUrlConfig urlConfig in context.databaseConfigs)\n                {\n                    ConfigNode node = urlConfig.Node;\n\n                    if (node.name != nodeType) continue;\n\n                    foundNodeType = true;\n\n                    if (nodeName == null || (node.GetValue(\"name\") is string testNodeName && WildcardMatch(testNodeName, nodeName)))\n                    {\n                        nodeStack = new NodeStack(node);\n                        break;\n                    }\n                }\n\n                if (!foundNodeType) context.logger.Warning(\"Can't find nodeType:\" + nodeType);\n                if (nodeStack == null) return null;\n            }\n            else\n            {\n                if (constraint.Length > 0)\n                {\n                    // get the first one matching\n                    ConfigNode last = null;\n                    while (true)\n                    {\n                        ConfigNode n = FindConfigNodeIn(nodeStack.value, nodeType, nodeName, index++);\n                        if (n == last || n == null)\n                        {\n                            nodeStack = null;\n                            break;\n                        }\n                        if (CheckConstraints(n, constraint))\n                        {\n                            nodeStack = nodeStack.Push(n);\n                            break;\n                        }\n                        last = n;\n                    }\n                }\n                else\n                {\n                    // just get one node\n                    nodeStack = nodeStack.Push(FindConfigNodeIn(nodeStack.value, nodeType, nodeName, index));\n                }\n            }\n\n            // XXXXXX/\n            if (nextSep > 0 && nodeStack != null)\n            {\n                path = path.Substring(nextSep + 1);\n                //log(\"NewPath : \\\"\" + path + \"\\\"\");\n                return RecurseNodeSearch(path, nodeStack, context);\n            }\n\n            return nodeStack.value;\n        }\n\n        // KeyName is group 1, index is group 2, value index is group 3, value separator is group 4\n        private static readonly Regex parseVarKey = new Regex(@\"([\\w\\&\\-\\.]+)(?:,((?:[0-9]+)+))?(?:\\[((?:[0-9]+)+)(?:,(.))?\\])?\");\n\n        // Search for a value by a path alike string\n        private static ConfigNode.Value RecurseVariableSearch(string path, NodeStack nodeStack, PatchContext context)\n        {\n            //log(\"path:\" + path);\n            if (path[0] == '/')\n                return RecurseVariableSearch(path.Substring(1), nodeStack.Root, context);\n            int nextSep = path.IndexOf('/');\n\n            // make sure we don't stop on a \",/\" which would be a value separator\n            // it's a hack that should be replaced with a proper regex for the whole node search\n            while (nextSep > 0 && path[nextSep - 1] == ',')\n                nextSep = path.IndexOf('/', nextSep + 1);\n\n            if (path[0] == '@')\n            {\n                if (nextSep < 2)\n                    return null;\n\n                string subName = path.Substring(1, nextSep - 1);\n                string nodeType, nodeName;\n\n                if (subName.Contains(\"[\"))\n                {\n                    // @NODETYPE[Name]/\n                    nodeType = subName.Split('[')[0];\n                    nodeName = subName.Split('[')[1].Replace(\"]\", \"\");\n                }\n                else\n                {\n                    // @NODETYPE/\n                    nodeType = subName;\n                    nodeName = null;\n                }\n\n                bool foundNodeType = false;\n                foreach (IProtoUrlConfig urlConfig in context.databaseConfigs)\n                {\n                    ConfigNode node = urlConfig.Node;\n\n                    if (node.name != nodeType) continue;\n\n                    foundNodeType = true;\n\n                    if (nodeName == null || (node.GetValue(\"name\") is string testNodeName && WildcardMatch(testNodeName, nodeName)))\n                    {\n                        return RecurseVariableSearch(path.Substring(nextSep + 1), new NodeStack(node), context);\n                    }\n                }\n\n                if (!foundNodeType) context.logger.Warning(\"Can't find nodeType:\" + nodeType);\n\n                return null;\n            }\n            if (path.StartsWith(\"../\"))\n            {\n                if (nodeStack.IsRoot)\n                    return null;\n\n                return RecurseVariableSearch(path.Substring(3), nodeStack.Pop(), context);\n            }\n\n            // Node search\n            if (nextSep > 0 && path[nextSep - 1] != ',')\n            {\n                // Big case of code duplication here ...\n                // TODO : replace with a regex\n\n                string subName = path.Substring(0, nextSep);\n                string constraint = \"\";\n                string nodeType, nodeName;\n                int index = 0;\n                if (subName.Contains(\":HAS[\", out int hasStart))\n                {\n                    constraint = subName.Substring(hasStart + 5, subName.LastIndexOf(']') - hasStart - 5);\n                    subName = subName.Substring(0, hasStart);\n                }\n                else if (subName.Contains(','))\n                {\n                    string tag = subName.Split(',')[1];\n                    subName = subName.Split(',')[0];\n                    int.TryParse(tag, out index);\n                }\n\n                if (subName.Contains(\"[\"))\n                {\n                    // format NODETYPE[Name] {...}\n                    // or NODETYPE[Name, index] {...}\n                    nodeType = subName.Split('[')[0];\n                    nodeName = subName.Split('[')[1].Replace(\"]\", \"\");\n                }\n                else\n                {\n                    // format NODETYPE {...}\n                    nodeType = subName;\n                    nodeName = null;\n                }\n\n                if (constraint.Length > 0)\n                {\n                    // get the first one matching\n                    ConfigNode last = null;\n                    while (true)\n                    {\n                        ConfigNode n = FindConfigNodeIn(nodeStack.value, nodeType, nodeName, index++);\n                        if (n == last || n == null)\n                            break;\n                        if (CheckConstraints(n, constraint))\n                            return RecurseVariableSearch(path.Substring(nextSep + 1), nodeStack.Push(n), context);\n                        last = n;\n                    }\n                    return null;\n                }\n                else\n                {\n                    // just get one node\n                    ConfigNode n = FindConfigNodeIn(nodeStack.value, nodeType, nodeName, index);\n                    if (n != null)\n                        return RecurseVariableSearch(path.Substring(nextSep + 1), nodeStack.Push(n), context);\n                    return null;\n                }\n            }\n\n            // Value search\n\n            Match match = parseVarKey.Match(path);\n            if (!match.Success)\n            {\n                context.logger.Warning(\"Cannot parse variable search command: \" + path);\n                return null;\n            }\n\n            string valName = match.Groups[1].Value;\n\n            int idx = 0;\n            if (match.Groups[2].Success)\n                int.TryParse(match.Groups[2].Value, out idx);\n\n            ConfigNode.Value cVal = FindValueIn(nodeStack.value, valName, idx);\n            if (cVal == null)\n            {\n                context.logger.Warning(\"Cannot find key \" + valName + \" in \" + nodeStack.value.name);\n                return null;\n            }\n\n            if (match.Groups[3].Success)\n            {\n                ConfigNode.Value newVal = new ConfigNode.Value(cVal.name, cVal.value);\n                int.TryParse(match.Groups[3].Value, out int splitIdx);\n\n                char sep = ',';\n                if (match.Groups[4].Success)\n                    sep = match.Groups[4].Value[0];\n                string[] split = newVal.value.Split(sep);\n                if (splitIdx < split.Length)\n                    newVal.value = split[splitIdx];\n                else\n                    newVal.value = \"\";\n                return newVal;\n            }\n            return cVal;\n        }\n\n        private static string ProcessVariableSearch(string value, NodeStack nodeStack, PatchContext context)\n        {\n            // value = #xxxx$yyyyy$zzzzz$aaaa$bbbb\n            // There is 2 or more '$'\n            if (value.Length > 0 && value[0] == '#' && value.IndexOf('$') != -1 && value.IndexOf('$') != value.LastIndexOf('$'))\n            {\n                //log(\"variable search input : =\\\"\" + value + \"\\\"\");\n                string[] split = value.Split('$');\n\n                if (split.Length % 2 != 1)\n                    return null;\n\n                StringBuilder builder = new StringBuilder();\n                builder.Append(split[0].Substring(1));\n\n                for (int i = 1; i < split.Length - 1; i += 2)\n                {\n                    ConfigNode.Value result = RecurseVariableSearch(split[i], nodeStack, context);\n                    if (result == null || result.value == null)\n                        return null;\n                    builder.Append(result.value);\n                    builder.Append(split[i + 1]);\n                }\n                value = builder.ToString();\n                //log(\"variable search output : =\\\"\" + value + \"\\\"\");\n            }\n            return value;\n        }\n\n        private static string FindAndReplaceValue(\n            ConfigNode mod,\n            ref string valName,\n            string value,\n            ConfigNode newNode,\n            Operator op,\n            int index,\n            out ConfigNode.Value origVal,\n            PatchContext context,\n            bool hasPosIndex = false,\n            int posIndex = 0,\n            bool hasPosStar = false,\n            char seperator = ',')\n        {\n            origVal = FindValueIn(newNode, valName, index);\n            if (origVal == null)\n                return null;\n            string oValue = origVal.value;\n\n            string[] strArray = new string[] { oValue };\n            if (hasPosIndex)\n            {\n                strArray = oValue.Split(new char[] { seperator }, StringSplitOptions.RemoveEmptyEntries);\n                if (posIndex >= strArray.Length)\n                {\n                    context.progress.Error(context.patchUrl, \"Invalid Vector Index!\");\n                    return null;\n                }\n            }\n            string backupValue = value;\n            while (posIndex < strArray.Length)\n            {\n                value = backupValue;\n                oValue = strArray[posIndex];\n                if (op != Operator.Assign)\n                {\n                    if (op == Operator.RegexReplace)\n                    {\n                        try\n                        {\n                            string[] split = value.Split(value[0]);\n\n                            Regex replace = regexCache.Fetch(split[1], delegate\n                            {\n                                return new Regex(split[1]);\n                            });\n\n                            value = replace.Replace(oValue, split[2]);\n                        }\n                        catch (Exception ex)\n                        {\n                            context.progress.Exception(context.patchUrl, \"Error - Failed to do a regexp replacement: \" + mod.name + \" : original value=\\\"\" + oValue +\n                                \"\\\" regexp=\\\"\" + value +\n                                \"\\\" \\nNote - to use regexp, the first char is used to subdivide the string (much like sed)\", ex);\n                            return null;\n                        }\n                    }\n                    else if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double s)\n                             && double.TryParse(oValue, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double os))\n                    {\n                        switch (op)\n                        {\n                            case Operator.Multiply:\n                                value = (os * s).ToString(CultureInfo.InvariantCulture);\n                                break;\n\n                            case Operator.Divide:\n                                value = (os / s).ToString(CultureInfo.InvariantCulture);\n                                break;\n\n                            case Operator.Add:\n                                value = (os + s).ToString(CultureInfo.InvariantCulture);\n                                break;\n\n                            case Operator.Subtract:\n                                value = (os - s).ToString(CultureInfo.InvariantCulture);\n                                break;\n\n                            case Operator.Exponentiate:\n                                value = Math.Pow(os, s).ToString(CultureInfo.InvariantCulture);\n                                break;\n                        }\n                    }\n                    else\n                    {\n                        context.progress.Error(context.patchUrl, \"Error - Failed to do a maths replacement: \" + mod.name + \" : original value=\\\"\" + oValue +\n                            \"\\\" operator=\" + op + \" mod value=\\\"\" + value + \"\\\"\");\n                        return null;\n                    }\n                }\n                strArray[posIndex] = value;\n                if (hasPosStar) posIndex++;\n                else break;\n            }\n            value = String.Join(new string(seperator, 1), strArray);\n            return value;\n        }\n\n        #endregion Applying Patches\n\n        #region Condition checking\n\n        // Split condiction while not getting lost in embeded brackets\n        public static List<string> SplitConstraints(string condition)\n        {\n            condition = condition.RemoveWS() + \",\";\n            List<string> conditions = new List<string>();\n            int start = 0;\n            int level = 0;\n            for (int end = 0; end < condition.Length; end++)\n            {\n                if ((condition[end] == ',' || condition[end] == '&') && level == 0)\n                {\n                    conditions.Add(condition.Substring(start, end - start));\n                    start = end + 1;\n                }\n                else if (condition[end] == '[')\n                    level++;\n                else if (condition[end] == ']')\n                    level--;\n            }\n            return conditions;\n        }\n\n        static readonly char[] contraintSeparators = { '[', ']' };\n\n        public static bool CheckConstraints(ConfigNode node, string constraints)\n        {\n            constraints = constraints.RemoveWS();\n\n            if (constraints.Length == 0)\n                return true;\n\n            List<string> constraintList = SplitConstraints(constraints);\n\n            if (constraintList.Count == 1)\n            {\n                constraints = constraintList[0];\n\n                string remainingConstraints = \"\";\n                if (constraints.Contains(\":HAS[\", out int hasStart))\n                {\n                    hasStart += 5;\n                    remainingConstraints = constraints.Substring(hasStart, constraintList[0].LastIndexOf(']') - hasStart);\n                    constraints = constraints.Substring(0, hasStart - 5);\n                }\n\n                string[] splits = constraints.Split(contraintSeparators, 3);\n                string type = splits[0].Substring(1);\n                string name = splits.Length > 1 ? splits[1] : null;\n\n                switch (constraints[0])\n                {\n                    case '@':\n                    case '!':\n\n                        // @MODULE[ModuleAlternator] or !MODULE[ModuleAlternator]\n                        bool not = (constraints[0] == '!');\n\n                        bool any = false;\n                        int index = 0;\n                        ConfigNode last = null;\n                        while (true)\n                        {\n                            ConfigNode subNode = FindConfigNodeIn(node, type, name, index++);\n                            if (subNode == last || subNode == null)\n                                break;\n                            any = any || CheckConstraints(subNode, remainingConstraints);\n                            last = subNode;\n                        }\n                        if (last != null)\n                        {\n                            //print(\"CheckConstraints: \" + constraints + \" \" + (not ^ any));\n                            return not ^ any;\n                        }\n                        //print(\"CheckConstraints: \" + constraints + \" \" + (not ^ false));\n                        return not ^ false;\n\n                    case '#':\n\n                        // #module[Winglet]\n                        if (node.HasValue(type) && WildcardMatchValues(node, type, name))\n                        {\n                            bool ret2 = CheckConstraints(node, remainingConstraints);\n                            //print(\"CheckConstraints: \" + constraints + \" \" + ret2);\n                            return ret2;\n                        }\n                        //print(\"CheckConstraints: \" + constraints + \" false\");\n                        return false;\n\n                    case '~':\n\n                        // ~breakingForce[]  breakingForce is not present\n                        // or: ~breakingForce[100]  will be true if it's present but not 100, too.\n                        if (name == \"\" && node.HasValue(type))\n                        {\n                            //print(\"CheckConstraints: \" + constraints + \" false\");\n                            return false;\n                        }\n                        if (name != \"\" && WildcardMatchValues(node, type, name))\n                        {\n                            //print(\"CheckConstraints: \" + constraints + \" false\");\n                            return false;\n                        }\n                        bool ret = CheckConstraints(node, remainingConstraints);\n                        //print(\"CheckConstraints: \" + constraints + \" \" + ret);\n                        return ret;\n\n                    default:\n                        //print(\"CheckConstraints: \" + constraints + \" false\");\n                        return false;\n                }\n            }\n\n            bool ret3 = true;\n            foreach (string constraint in constraintList)\n            {\n                ret3 = ret3 && CheckConstraints(node, constraint);\n            }\n            //print(\"CheckConstraints: \" + constraints + \" \" + ret3);\n            return ret3;\n        }\n\n        public static bool WildcardMatchValues(ConfigNode node, string type, string value)\n        {\n            double val = 0;\n            bool compare = value != null && value.Length > 1 && (value[0] == '<' || value[0] == '>');\n            compare = compare && double.TryParse(value.Substring(1), NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out val);\n\n            string[] values = node.GetValues(type);\n            for (int i = 0; i < values.Length; i++)\n            {\n                if (!compare && WildcardMatch(values[i], value))\n                    return true;\n\n                if (compare && double.TryParse(values[i], NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double val2)\n                    && ((value[0] == '<' && val2 < val) || (value[0] == '>' && val2 > val)))\n                {\n                    return true;\n                }\n            }\n            return false;\n        }\n\n        public static bool WildcardMatch(string s, string wildcard)\n        {\n            if (wildcard == null)\n                return true;\n            string pattern = \"^\" + Regex.Escape(wildcard).Replace(@\"\\*\", \".*\").Replace(@\"\\?\", \".\") + \"$\";\n\n            Regex regex = regexCache.Fetch(pattern, delegate\n            {\n                return new Regex(pattern);\n            });\n            return regex.IsMatch(s);\n        }\n\n        #endregion Condition checking\n\n        #region Config Node Utilities\n\n        private static void InsertNode(ConfigNode newNode, ConfigNode subMod, int index)\n        {\n            string modName = subMod.name;\n\n            ConfigNode[] oldValues = newNode.GetNodes(modName);\n            if (index < oldValues.Length)\n            {\n                newNode.RemoveNodes(modName);\n                int i = 0;\n                for (; i < index; ++i)\n                    newNode.AddNode(oldValues[i]);\n                newNode.AddNode(subMod);\n                for (; i < oldValues.Length; ++i)\n                    newNode.AddNode(oldValues[i]);\n            }\n            else\n                newNode.AddNode(subMod);\n        }\n\n        private static void InsertValue(ConfigNode newNode, int index, string name, string value)\n        {\n            string[] oldValues = newNode.GetValues(name);\n            if (index < oldValues.Length)\n            {\n                newNode.RemoveValues(name);\n                int i = 0;\n                for (; i < index; ++i)\n                    newNode.AddValueSafe(name, oldValues[i]);\n                newNode.AddValueSafe(name, value);\n                for (; i < oldValues.Length; ++i)\n                    newNode.AddValueSafe(name, oldValues[i]);\n                return;\n            }\n            newNode.AddValueSafe(name, value);\n        }\n\n        //FindConfigNodeIn finds and returns a ConfigNode in src of type nodeType.\n        //If nodeName is not null, it will only find a node of type nodeType with the value name=nodeName.\n        //If nodeTag is not null, it will only find a node of type nodeType with the value name=nodeName and tag=nodeTag.\n        public static ConfigNode FindConfigNodeIn(\n            ConfigNode src,\n            string nodeType,\n            string nodeName = null,\n            int index = 0)\n        {\n            List<ConfigNode> nodes = new List<ConfigNode>();\n            int c = src.nodes.Count;\n            for(int i = 0; i < c; ++i)\n            {\n                if (WildcardMatch(src.nodes[i].name, nodeType))\n                    nodes.Add(src.nodes[i]);\n            }\n            int nodeCount = nodes.Count;\n            if (nodeCount == 0)\n                return null;\n            if (nodeName == null)\n            {\n                if (index >= 0)\n                    return nodes[Math.Min(index, nodeCount - 1)];\n                return nodes[Math.Max(0, nodeCount + index)];\n            }\n            ConfigNode last = null;\n            if (index >= 0)\n            {\n                for (int i = 0; i < nodeCount; ++i)\n                {\n                    if (nodes[i].HasValue(\"name\") && WildcardMatch(nodes[i].GetValue(\"name\"), nodeName))\n                    {\n                        last = nodes[i];\n                        if (--index < 0)\n                            return last;\n                    }\n                }\n                return last;\n            }\n            for (int i = nodeCount - 1; i >= 0; --i)\n            {\n                if (nodes[i].HasValue(\"name\") && WildcardMatch(nodes[i].GetValue(\"name\"), nodeName))\n                {\n                    last = nodes[i];\n                    if (++index >= 0)\n                        return last;\n                }\n            }\n            return last;\n        }\n\n        private static ConfigNode.Value FindValueIn(ConfigNode newNode, string valName, int index)\n        {\n            ConfigNode.Value v = null;\n            for (int i = 0; i < newNode.values.Count; ++i)\n            {\n                if (WildcardMatch(newNode.values[i].name, valName))\n                {\n                    v = newNode.values[i];\n                    if (--index < 0)\n                        return v;\n                }\n            }\n            return v;\n        }\n\n        #endregion Config Node Utilities\n    }\n}\n"
  },
  {
    "path": "ModuleManager/MMPatchRunner.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.IO;\nusing ModuleManager.Collections;\nusing ModuleManager.Extensions;\nusing ModuleManager.Logging;\nusing ModuleManager.Threading;\n\nusing static ModuleManager.FilePathRepository;\n\nnamespace ModuleManager\n{\n    public class MMPatchRunner\n    {\n        private readonly IBasicLogger kspLogger;\n\n        public string Status { get; private set; } = \"\";\n        public string Errors { get; private set; } = \"\";\n\n        public MMPatchRunner(IBasicLogger kspLogger)\n        {\n            this.kspLogger = kspLogger ?? throw new ArgumentNullException(nameof(kspLogger));\n        }\n\n        public IEnumerator Run()\n        {\n            PostPatchLoader.Instance.databaseConfigs = null;\n\n            if (!Directory.Exists(logsDirPath)) Directory.CreateDirectory(logsDirPath);\n\n            kspLogger.Info(\"Patching started on a new thread, all output will be directed to \" + logPath);\n\n            MessageQueue<ILogMessage> mmLogQueue = new MessageQueue<ILogMessage>();\n            QueueLogRunner logRunner = new QueueLogRunner(mmLogQueue);\n            ITaskStatus loggingThreadStatus = BackgroundTask.Start(delegate\n            {\n                using StreamLogger streamLogger = new StreamLogger(new FileStream(logPath, FileMode.Create));\n                streamLogger.Info(\"Log started at \" + DateTime.Now.ToString(\"yyyy-MM-dd HH:mm:ss.fff\"));\n                logRunner.Run(streamLogger);\n                streamLogger.Info(\"Done!\");\n            });\n\n            // Wait for game database to be initialized for the 2nd time and wait for any plugins to initialize\n            yield return null;\n            yield return null;\n\n            IBasicLogger mmLogger = new QueueLogger(mmLogQueue);\n\n            IEnumerable<ModListGenerator.ModAddedByAssembly> modsAddedByAssemblies = ModListGenerator.GetAdditionalModsFromStaticMethods(mmLogger);\n\n            IEnumerable<IProtoUrlConfig> databaseConfigs = null;\n\n            MMPatchLoader patchLoader = new MMPatchLoader(modsAddedByAssemblies, mmLogger);\n\n            ITaskStatus patchingThreadStatus = BackgroundTask.Start(delegate\n            {\n                databaseConfigs = patchLoader.Run();\n            });\n\n            while(true)\n            {\n                yield return null;\n\n                if (!patchingThreadStatus.IsRunning)\n                    logRunner.RequestStop();\n\n                Status = patchLoader.status;\n                Errors = patchLoader.errors;\n\n                if (!patchingThreadStatus.IsRunning && !loggingThreadStatus.IsRunning) break;\n            }\n\n            if (patchingThreadStatus.IsExitedWithError)\n            {\n                kspLogger.Exception(\"The patching thread threw an exception\", patchingThreadStatus.Exception);\n                FatalErrorHandler.HandleFatalError(\"The patching thread threw an exception\");\n                yield break;\n            }\n\n            if (loggingThreadStatus.IsExitedWithError)\n            {\n                kspLogger.Exception(\"The logging thread threw an exception\", loggingThreadStatus.Exception);\n                FatalErrorHandler.HandleFatalError(\"The logging thread threw an exception\");\n                yield break;\n            }\n\n            if (databaseConfigs == null)\n            {\n                kspLogger.Error(\"The patcher returned a null collection of configs\");\n                FatalErrorHandler.HandleFatalError(\"The patcher returned a null collection of configs\");\n                yield break;\n            }\n\n            PostPatchLoader.Instance.databaseConfigs = databaseConfigs;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/ModListGenerator.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Diagnostics;\nusing System.Reflection;\nusing UnityEngine;\nusing ModuleManager.Extensions;\nusing ModuleManager.Logging;\nusing ModuleManager.Utils;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager\n{\n    public static class ModListGenerator\n    {\n        public static IEnumerable<string> GenerateModList(IEnumerable<ModAddedByAssembly> modsAddedByAssemblies, IPatchProgress progress, IBasicLogger logger)\n        {\n            #region List of mods\n\n            //string envInfo = \"ModuleManager env info\\n\";\n            //envInfo += \"  \" + Environment.OSVersion.Platform + \" \" + ModuleManager.intPtr.ToInt64().ToString(\"X16\") + \"\\n\";\n            //envInfo += \"  \" + Convert.ToString(ModuleManager.intPtr.ToInt64(), 2)  + \" \" + Convert.ToString(ModuleManager.intPtr.ToInt64() >> 63, 2) + \"\\n\";\n            //string gamePath = Environment.GetCommandLineArgs()[0];\n            //envInfo += \"  Args: \" + gamePath.Split(Path.DirectorySeparatorChar).Last() + \" \" + string.Join(\" \", Environment.GetCommandLineArgs().Skip(1).ToArray()) + \"\\n\";\n            //envInfo += \"  Executable SHA256 \" + FileSHA(gamePath);\n            //\n            //log(envInfo);\n\n            List<string> mods = new List<string>();\n\n            StringBuilder modListInfo = new StringBuilder();\n\n            modListInfo.Append(\"compiling list of loaded mods...\\nMod DLLs found:\\n\");\n\n            string format = \"  {0,-40}{1,-25}{2,-25}{3,-25}{4}\\n\";\n\n            modListInfo.AppendFormat(\n                format,\n                \"Name\",\n                \"Assembly Version\",\n                \"Assembly File Version\",\n                \"KSPAssembly Version\",\n                \"SHA256\"\n            );\n\n            modListInfo.Append('\\n');\n\n            foreach (AssemblyLoader.LoadedAssembly mod in AssemblyLoader.loadedAssemblies)\n            {\n\n                if (string.IsNullOrEmpty(mod.assembly.Location)) //Diazo Edit for xEvilReeperx AssemblyReloader mod\n                    continue;\n\n                FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(mod.assembly.Location);\n\n                AssemblyName assemblyName = mod.assembly.GetName();\n\n                string kspAssemblyVersion;\n                if (mod.versionMajor == 0 && mod.versionMinor == 0)\n                    kspAssemblyVersion = \"\";\n                else\n                    kspAssemblyVersion = mod.versionMajor + \".\" + mod.versionMinor;\n\n                string fileSha = \"\";\n                try\n                {\n                    fileSha = FileUtils.FileSHA(mod.assembly.Location);\n                }\n                catch (Exception e)\n                {\n                    progress.Exception(\"Exception while generating SHA for assembly \" + assemblyName.Name, e);\n                }\n\n                modListInfo.AppendFormat(\n                    format,\n                    assemblyName.Name,\n                    assemblyName.Version,\n                    fileVersionInfo.FileVersion,\n                    kspAssemblyVersion,\n                    fileSha\n                );\n\n                // modlist += String.Format(\"  {0,-50} SHA256 {1}\\n\", modInfo, FileSHA(mod.assembly.Location));\n\n                if (!mods.Contains(assemblyName.Name, StringComparer.OrdinalIgnoreCase))\n                    mods.Add(assemblyName.Name);\n            }\n\n            modListInfo.Append(\"Non-DLL mods added (:FOR[xxx]):\\n\");\n            foreach (UrlDir.UrlConfig cfgmod in GameDatabase.Instance.root.AllConfigs)\n            {\n                if (CommandParser.Parse(cfgmod.type, out string name) != Command.Insert)\n                {\n                    if (name.Contains(\":FOR[\"))\n                    {\n                        name = name.RemoveWS();\n\n                        // check for FOR[] blocks that don't match loaded DLLs and add them to the pass list\n                        try\n                        {\n                            string dependency = name.Substring(name.IndexOf(\":FOR[\") + 5);\n                            dependency = dependency.Substring(0, dependency.IndexOf(']'));\n                            if (!mods.Contains(dependency, StringComparer.OrdinalIgnoreCase))\n                            {\n                                // found one, now add it to the list.\n                                mods.Add(dependency);\n                                modListInfo.AppendFormat(\"  {0}\\n\", dependency);\n                            }\n                        }\n                        catch (ArgumentOutOfRangeException)\n                        {\n                            progress.Error(cfgmod, \"Skipping :FOR init for line \" + name +\n                                \". The line most likely contains a space that should be removed\");\n                        }\n                    }\n                }\n            }\n            modListInfo.Append(\"Mods by directory (sub directories of GameData):\\n\");\n            UrlDir gameData = GameDatabase.Instance.root.children.First(dir => dir.type == UrlDir.DirectoryType.GameData);\n            foreach (UrlDir subDir in gameData.children)\n            {\n                string cleanName = subDir.name.RemoveWS();\n                if (!mods.Contains(cleanName, StringComparer.OrdinalIgnoreCase))\n                {\n                    mods.Add(cleanName);\n                    modListInfo.AppendFormat(\"  {0}\\n\", cleanName);\n                }\n            }\n\n            modListInfo.Append(\"Mods added by assemblies:\\n\");\n            foreach (ModAddedByAssembly mod in modsAddedByAssemblies)\n            {\n                if (!mods.Contains(mod.modName, StringComparer.OrdinalIgnoreCase))\n                {\n                    mods.Add(mod.modName);\n                    modListInfo.AppendFormat(\"  {0}\\n\", mod);\n                }\n            }\n\n            logger.Info(modListInfo.ToString());\n\n            mods.Sort();\n\n            #endregion List of mods\n\n            return mods;\n        }\n\n        public class ModAddedByAssembly\n        {\n            public readonly string modName;\n            public readonly string assemblyName;\n\n            public ModAddedByAssembly(string modName, string assemblyName)\n            {\n                this.modName = modName ?? throw new ArgumentNullException(nameof(modName));\n                this.assemblyName = assemblyName ?? throw new ArgumentNullException(nameof(assemblyName));\n            }\n\n            public override string ToString()\n            {\n                return $\"{modName} (added by {assemblyName})\";\n            }\n        }\n\n        public static IEnumerable<ModAddedByAssembly> GetAdditionalModsFromStaticMethods(IBasicLogger logger)\n        {\n            List<ModAddedByAssembly> result = new List<ModAddedByAssembly>();\n            foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies())\n            {\n                try\n                {\n                    foreach (Type type in ass.GetTypes())\n                    {\n                        MethodInfo method = type.GetMethod(\"ModuleManagerAddToModList\", BindingFlags.Public | BindingFlags.Static);\n\n                        if (method != null && method.GetParameters().Length == 0 && typeof(IEnumerable<string>).IsAssignableFrom(method.ReturnType))\n                        {\n                            string methodName = $\"{ass.GetName().Name}.{type.Name}.{method.Name}()\";\n                            try\n                            {\n                                logger.Info(\"Calling \" + methodName);\n                                IEnumerable<string> modsToAdd = (IEnumerable<string>)method.Invoke(null, null);\n\n                                if (modsToAdd == null)\n                                {\n                                    logger.Error(\"ModuleManagerAddToModList returned null: \" + methodName);\n                                    continue;\n                                }\n\n                                foreach (string mod in modsToAdd)\n                                {\n                                    result.Add(new ModAddedByAssembly(mod, ass.GetName().Name));\n                                }\n                            }\n                            catch (Exception e)\n                            {\n                                logger.Exception(\"Exception while calling \" + methodName, e);\n                            }\n                        }\n                    }\n                }\n                catch (Exception e)\n                {\n                    logger.Exception(\"Add to mod list threw an exception in loading \" + ass.FullName, e);\n                }\n            }\n\n            foreach (MonoBehaviour obj in UnityEngine.Object.FindObjectsOfType<MonoBehaviour>())\n            {\n                MethodInfo method = obj.GetType().GetMethod(\"ModuleManagerAddToModList\", BindingFlags.Public | BindingFlags.Instance);\n\n                if (method != null && method.GetParameters().Length == 0 && typeof(IEnumerable<string>).IsAssignableFrom(method.ReturnType))\n                {\n                    string methodName = $\"{obj.GetType().Name}.{method.Name}()\";\n                    try\n                    {\n                        logger.Info(\"Calling \" + methodName);\n                        IEnumerable<string> modsToAdd = (IEnumerable<string>)method.Invoke(obj, null);\n\n                        if (modsToAdd == null)\n                        {\n                            logger.Error(\"ModuleManagerAddToModList returned null: \" + methodName);\n                            continue;\n                        }\n\n                        foreach (string mod in modsToAdd)\n                        {\n                            result.Add(new ModAddedByAssembly(mod, obj.GetType().Assembly.GetName().Name));\n                        }\n                    }\n                    catch (Exception e)\n                    {\n                        logger.Exception(\"Exception while calling \" + methodName, e);\n                    }\n                }\n            }\n\n            return result;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/ModuleManager.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Diagnostics.CodeAnalysis;\nusing System.IO;\nusing System.Linq;\nusing System.Reflection;\nusing TMPro;\nusing UnityEngine;\nusing Debug = UnityEngine.Debug;\nusing ModuleManager.Cats;\nusing ModuleManager.Extensions;\nusing ModuleManager.Logging;\nusing ModuleManager.UnityLogHandle;\n\nnamespace ModuleManager\n{\n    [KSPAddon(KSPAddon.Startup.Instantly, true)]\n    public class ModuleManager : MonoBehaviour\n    {\n        #region state\n\n        private bool inRnDCenter;\n\n        public bool showUI = false;\n        private float textPos = 0;\n\n        //private Texture2D tex;\n        //private Texture2D tex2;\n\n        private bool nyan = false;\n        private bool nCats = false;\n        public static bool dumpPostPatch = false;\n        public static bool DontCopyLogs { get; private set; } = false;\n\n        private PopupDialog menu;\n\n        private MMPatchRunner patchRunner;\n\n        #endregion state\n\n        private static bool loadedInScene;\n\n        internal void OnRnDCenterSpawn()\n        {\n            inRnDCenter = true;\n        }\n\n        internal void OnRnDCenterDeSpawn()\n        {\n            inRnDCenter = false;\n        }\n\n        public static void Log(String s)\n        {\n            print(\"[ModuleManager] \" + s);\n        }\n\n        private readonly Stopwatch totalTime = new Stopwatch();\n\n        internal void Awake()\n        {\n            if (LoadingScreen.Instance == null)\n            {\n                Destroy(gameObject);\n                return;\n            }\n\n            // Ensure that only one copy of the service is run per scene change.\n            if (loadedInScene || !ElectionAndCheck())\n            {\n                Assembly currentAssembly = Assembly.GetExecutingAssembly();\n                Log(\"Multiple copies of current version. Using the first copy. Version: \" +\n                    currentAssembly.GetName().Version);\n                Destroy(gameObject);\n                return;\n            }\n\n            totalTime.Start();\n\n            Debug.unityLogger.logHandler = new InterceptLogHandler(Debug.unityLogger.logHandler);\n\n            // Allow loading the background in the loading screen\n            Application.runInBackground = true;\n            QualitySettings.vSyncCount = 0;\n            Application.targetFrameRate = -1;\n\n            // More cool loading screen. Less 4 stoke logo.\n            for (int i = 0; i < LoadingScreen.Instance.Screens.Count; i++)\n            {\n                var state = LoadingScreen.Instance.Screens[i];\n                state.fadeInTime = i < 3 ? 0.1f : 1;\n                state.displayTime = i < 3 ? 1 : 3;\n                state.fadeOutTime = i < 3 ? 0.1f : 1;\n            }\n\n            TextMeshProUGUI[] texts = LoadingScreen.Instance.gameObject.GetComponentsInChildren<TextMeshProUGUI>();\n            foreach (var text in texts)\n            {\n                textPos = Mathf.Min(textPos, text.rectTransform.localPosition.y);\n            }\n            DontDestroyOnLoad(gameObject);\n\n            // Subscribe to the RnD center spawn/deSpawn events\n            GameEvents.onGUIRnDComplexSpawn.Add(OnRnDCenterSpawn);\n            GameEvents.onGUIRnDComplexDespawn.Add(OnRnDCenterDeSpawn);\n\n\n            LoadingScreen screen = FindObjectOfType<LoadingScreen>();\n            if (screen == null)\n            {\n                Log(\"Can't find LoadingScreen type. Aborting ModuleManager execution\");\n                return;\n            }\n            List<LoadingSystem> list = LoadingScreen.Instance.loaders;\n\n            if (list != null)\n            {\n                // So you can insert a LoadingSystem object in this list at any point.\n                // GameDatabase is first in the list, and PartLoader is second\n                // We could insert ModuleManager after GameDatabase to get it to run there\n                // and SaveGameFixer after PartLoader.\n\n                int gameDatabaseIndex = list.FindIndex(s => s is GameDatabase);\n\n                GameObject aGameObject = new GameObject(\"ModuleManager\");\n                DontDestroyOnLoad(aGameObject);\n\n                Log(string.Format(\"Adding post patch to the loading screen {0}\", list.Count));\n                list.Insert(gameDatabaseIndex + 1, aGameObject.AddComponent<PostPatchLoader>());\n\n                patchRunner = new MMPatchRunner(new PrefixLogger(\"ModuleManager\", new UnityLogger(Debug.unityLogger)));\n                StartCoroutine(patchRunner.Run());\n\n                // Workaround for 1.6.0 Editor bug after a PartDatabase rebuild.\n                if (Versioning.version_major == 1 && Versioning.version_minor == 6 && Versioning.Revision == 0)\n                {\n                    Fix16 fix16 = aGameObject.AddComponent<Fix16>();\n                    list.Add(fix16);\n                }\n            }\n\n            bool foolsDay = (DateTime.Now.Month == 4 && DateTime.Now.Day == 1);\n            bool catDay = (DateTime.Now.Month == 2 && DateTime.Now.Day == 22);\n            nyan = foolsDay\n                || Environment.GetCommandLineArgs().Contains(\"-nyan-nyan\");\n\n            nCats = catDay\n                || Environment.GetCommandLineArgs().Contains(\"-ncats\");\n\n            dumpPostPatch = Environment.GetCommandLineArgs().Contains(\"-mm-dump\");\n\n            DontCopyLogs = Environment.GetCommandLineArgs().Contains(\"-mm-dont-copy-logs\");\n\n            loadedInScene = true;\n        }\n\n        private TextMeshProUGUI status;\n        private TextMeshProUGUI errors;\n        private TextMeshProUGUI warning;\n\n        [SuppressMessage(\"Code Quality\", \"IDE0051\", Justification = \"Called by Unity\")]\n        private void Start()\n        {\n            if (nCats)\n                CatManager.LaunchCats();\n            else if (nyan)\n                CatManager.LaunchCat();\n\n            Canvas canvas = LoadingScreen.Instance.GetComponentInChildren<Canvas>();\n\n            status = CreateTextObject(canvas, \"MMStatus\");\n            errors = CreateTextObject(canvas, \"MMErrors\");\n            warning = CreateTextObject(canvas, \"MMWarning\");\n            warning.text = \"\";\n\n            //if (Versioning.version_major == 1 && Versioning.version_minor == 0 && Versioning.Revision == 5 && Versioning.BuildID == 1024)\n            //{\n            //    warning.text = \"Your KSP 1.0.5 is running on build 1024. You should upgrade to build 1028 to avoid problems with addons.\";\n            //    //if (GUI.Button(new Rect(Screen.width / 2f - 100, offsetY, 200, 20), \"Click to open the Forum thread\"))\n            //    //    Application.OpenURL(\"http://forum.kerbalspaceprogram.com/index.php?/topic/124998-silent-patch-for-ksp-105-published/\");\n            //}\n\n            if (Versioning.version_major == 1 && Versioning.version_minor >= 8)\n            {\n                foreach (AssemblyLoader.LoadedAssembly assembly in AssemblyLoader.loadedAssemblies)\n                {\n                    AssemblyName assemblyName = assembly.assembly.GetName();\n                    if (assemblyName.Name == \"Firespitter\" && assemblyName.Version <= Version.Parse(\"7.3.7175.38653\"))\n                    {\n                        warning.text = \"You are using a version of Firespitter that does not run properly on KSP 1.8+\\nThis version may prevent the game from loading properly and may create problems for other mods\";\n                    }\n                }\n            }\n        }\n\n        private TextMeshProUGUI CreateTextObject(Canvas canvas, string name)\n        {\n            GameObject statusGameObject = new GameObject(name);\n            TextMeshProUGUI text = statusGameObject.AddComponent<TextMeshProUGUI>();\n            text.text = \"STATUS\";\n            text.fontSize = 18;\n            text.autoSizeTextContainer = true;\n            text.font = Resources.Load(\"Fonts/Calibri SDF\", typeof(TMP_FontAsset)) as TMP_FontAsset;\n            text.alignment = TextAlignmentOptions.Center;\n            text.enableWordWrapping = false;\n            text.isOverlay = true;\n            text.rectTransform.anchorMin = new Vector2(0.5f, 0);\n            text.rectTransform.anchorMax = new Vector2(0.5f, 0);\n            text.rectTransform.anchoredPosition = Vector2.zero;\n            statusGameObject.transform.SetParent(canvas.transform);\n\n            return text;\n        }\n\n        // Unsubscribe from events when the behavior dies\n        internal void OnDestroy()\n        {\n            GameEvents.onGUIRnDComplexSpawn.Remove(OnRnDCenterSpawn);\n            GameEvents.onGUIRnDComplexDespawn.Remove(OnRnDCenterDeSpawn);\n        }\n\n        internal void Update()\n        {\n            if (GameSettings.MODIFIER_KEY.GetKey() && Input.GetKeyDown(KeyCode.F11)\n                && (HighLogic.LoadedScene == GameScenes.SPACECENTER || HighLogic.LoadedScene == GameScenes.MAINMENU)\n                && !inRnDCenter)\n            {\n                if (menu == null)\n                {\n                    menu = PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f),\n                        new Vector2(0.5f, 0.5f),\n                        new MultiOptionDialog(\n                            \"ModuleManagerMenu\",\n                            \"\",\n                            \"ModuleManager\",\n                            HighLogic.UISkin,\n                            new Rect(0.5f, 0.5f, 150f, 60f),\n                            new DialogGUIFlexibleSpace(),\n                            new DialogGUIVerticalLayout(\n                                new DialogGUIFlexibleSpace(),\n                                new DialogGUIButton(\"Reload Database\",\n                                    delegate\n                                    {\n                                        MMPatchLoader.keepPartDB = false;\n                                        StartCoroutine(DataBaseReloadWithMM());\n                                    }, 140.0f, 30.0f, true),\n                                new DialogGUIButton(\"Quick Reload Database\",\n                                    delegate\n                                    {\n                                        MMPatchLoader.keepPartDB = true;\n                                        StartCoroutine(DataBaseReloadWithMM());\n                                    }, 140.0f, 30.0f, true),\n                                new DialogGUIButton(\"Dump Database to Files\",\n                                    delegate\n                                    {\n                                        StartCoroutine(DataBaseReloadWithMM(true));\n                                    }, 140.0f, 30.0f, true),\n                                new DialogGUIButton(\"Close\", () => { }, 140.0f, 30.0f, true)\n                                )),\n                        false,\n                        HighLogic.UISkin);\n                }\n                else\n                {\n                    menu.Dismiss();\n                    menu = null;\n                }\n            }\n\n            if (totalTime.IsRunning && HighLogic.LoadedScene == GameScenes.MAINMENU)\n            {\n                totalTime.Stop();\n                Log(\"Total loading Time = \" + ((float)totalTime.ElapsedMilliseconds / 1000).ToString(\"F3\") + \"s\");\n\n                Application.runInBackground = GameSettings.SIMULATE_IN_BACKGROUND;\n                QualitySettings.vSyncCount = GameSettings.SYNC_VBL;\n                Application.targetFrameRate = GameSettings.FRAMERATE_LIMIT;\n            }\n\n            float offsetY = textPos;\n            float h;\n\n            if (patchRunner != null)\n            {\n                if (warning)\n                {\n                    warning.text = InterceptLogHandler.Warnings;\n                    h = warning.text.Length > 0 ? warning.textBounds.size.y : 0;\n                    offsetY += h;\n                    warning.rectTransform.localPosition = new Vector3(0, offsetY);\n                }\n\n                if (status)\n                {\n                    status.text = patchRunner.Status;\n                    h = status.text.Length > 0 ? status.textBounds.size.y : 0;\n                    offsetY += h;\n                    status.transform.localPosition = new Vector3(0, offsetY);\n                }\n\n                if (errors)\n                {\n                    errors.text = patchRunner.Errors;\n                    h = errors.text.Length > 0 ? errors.textBounds.size.y : 0;\n                    offsetY += h;\n                    errors.transform.localPosition = new Vector3(0, offsetY);\n                }\n            }\n        }\n\n        #region GUI stuff.\n\n        internal static IntPtr intPtr = new IntPtr(long.MaxValue);\n        /* Not required anymore. At least\n        public static bool IsABadIdea()\n        {\n            return (intPtr.ToInt64() == long.MaxValue) && (Environment.OSVersion.Platform == PlatformID.Win32NT);\n        }\n        */\n\n        private IEnumerator DataBaseReloadWithMM(bool dump = false)\n        {\n            QualitySettings.vSyncCount = 0;\n            Application.targetFrameRate = -1;\n\n            patchRunner = new MMPatchRunner(new PrefixLogger(\"ModuleManager\", new UnityLogger(Debug.unityLogger)));\n\n            float totalLoadWeight = GameDatabase.Instance.LoadWeight() + PartLoader.Instance.LoadWeight();\n            bool startedReload = false;\n\n            UISkinDef skinDef = HighLogic.UISkin;\n            UIStyle centeredTextStyle = new UIStyle(skinDef.label)\n            {\n                alignment = TextAnchor.UpperCenter\n            };\n\n            PopupDialog reloadingDialog = PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f),\n                new Vector2(0.5f, 0.5f),\n                new MultiOptionDialog(\n                    \"ModuleManagerReloading\",\n                    \"\",\n                    \"ModuleManager - Reloading Database\",\n                    skinDef,\n                    new Rect(0.5f, 0.5f, 600f, 60f),\n                    new DialogGUIFlexibleSpace(),\n                    new DialogGUIVerticalLayout(\n                        new DialogGUIFlexibleSpace(),\n                        new DialogGUILabel(delegate ()\n                        {\n                            float progressFraction;\n                            if (!startedReload)\n                            {\n                                progressFraction = 0f;\n                            }\n                            else if (!GameDatabase.Instance.IsReady() || !PostPatchLoader.Instance.IsReady())\n                            {\n                                progressFraction = GameDatabase.Instance.ProgressFraction() * GameDatabase.Instance.LoadWeight();\n                                progressFraction /= totalLoadWeight;\n                            }\n                            else if (!PartLoader.Instance.IsReady())\n                            {\n                                progressFraction = GameDatabase.Instance.LoadWeight() + (PartLoader.Instance.ProgressFraction() * GameDatabase.Instance.LoadWeight());\n                                progressFraction /= totalLoadWeight;\n                            }\n                            else\n                            {\n                                progressFraction = 1f;\n                            }\n\n                            return $\"Overall progress: {progressFraction:P0}\";\n                        }, centeredTextStyle, expandW: true),\n                        new DialogGUILabel(delegate ()\n                        {\n                            if (!startedReload)\n                                return \"Starting\";\n                            else if (!GameDatabase.Instance.IsReady())\n                                return GameDatabase.Instance.ProgressTitle();\n                            else if (!PostPatchLoader.Instance.IsReady())\n                                return PostPatchLoader.Instance.ProgressTitle();\n                            else if (!PartLoader.Instance.IsReady())\n                                return PartLoader.Instance.ProgressTitle();\n                            else\n                                return \"\";\n                        }),\n                        new DialogGUISpace(5f),\n                        new DialogGUILabel(() => patchRunner.Status)\n                    )\n                ),\n                false,\n                skinDef);\n\n            yield return null;\n\n            GameDatabase.Instance.Recompile = true;\n            GameDatabase.Instance.StartLoad();\n\n            startedReload = true;\n\n            yield return null;\n            StartCoroutine(patchRunner.Run());\n\n            // wait for it to finish\n            while (!GameDatabase.Instance.IsReady())\n                yield return null;\n\n            PostPatchLoader.Instance.StartLoad();\n\n            while (!PostPatchLoader.Instance.IsReady())\n                yield return null;\n\n            if (dump)\n                OutputAllConfigs();\n\n            PartLoader.Instance.StartLoad();\n\n            while (!PartLoader.Instance.IsReady())\n                yield return null;\n\n            // Needs more work.\n            //ConfigNode game = HighLogic.CurrentGame.config.GetNode(\"GAME\");\n\n            //if (game != null && ResearchAndDevelopment.Instance != null)\n            //{\n            //    ScreenMessages.PostScreenMessage(\"GAME found\");\n            //    ConfigNode scenario = game.GetNodes(\"SCENARIO\").FirstOrDefault((ConfigNode n) => n.name == \"ResearchAndDevelopment\");\n            //    if (scenario != null)\n            //    {\n            //        ScreenMessages.PostScreenMessage(\"SCENARIO found\");\n            //        ResearchAndDevelopment.Instance.OnLoad(scenario);\n            //    }\n            //}\n\n            QualitySettings.vSyncCount = GameSettings.SYNC_VBL;\n            Application.targetFrameRate = GameSettings.FRAMERATE_LIMIT;\n\n            reloadingDialog.Dismiss();\n        }\n\n        public static void OutputAllConfigs()\n        {\n            string path = Path.GetFullPath(Path.Combine(KSPUtil.ApplicationRootPath, \"_MMCfgOutput\"));\n            try\n            {\n                Directory.CreateDirectory(path);\n                foreach (string file in Directory.GetFiles(path))\n                {\n                    File.Delete(file);\n                }\n                foreach (string dir in Directory.GetDirectories(path))\n                {\n                    Directory.Delete(dir, true);\n                }\n            }\n            catch (IOException ioException)\n            {\n                Log(\"Exception while cleaning the export dir\\n\" + ioException);\n            }\n            catch (UnauthorizedAccessException unauthorizedAccessException)\n            {\n                Log(\"Exception while cleaning the export dir\\n\" + unauthorizedAccessException);\n            }\n\n            static void WriteDirectoryRecursive(UrlDir currentDir, string dirPath)\n            {\n                if (currentDir.files.Count > 0) Directory.CreateDirectory(dirPath);\n\n                foreach (UrlDir.UrlFile urlFile in currentDir.files)\n                {\n                    if (urlFile.fileType != UrlDir.FileType.Config) continue;\n\n                    Log(\"Exporting \" + urlFile.GetUrlWithExtension());\n                    string filePath = Path.Combine(dirPath, urlFile.GetNameWithExtension());\n\n                    bool first = true;\n\n                    using FileStream stream = new FileStream(filePath, FileMode.Create);\n                    using StreamWriter writer = new StreamWriter(stream);\n                    foreach (UrlDir.UrlConfig urlConfig in urlFile.configs)\n                    {\n                        try\n                        {\n                            if (first) first = false;\n                            else writer.Write(\"\\n\");\n\n                            ConfigNode copy = urlConfig.config.DeepCopy();\n                            copy.EscapeValuesRecursive();\n                            writer.Write(copy.ToString());\n                        }\n                        catch (Exception e)\n                        {\n                            Log(\"Exception while trying to write the file \" + filePath + \"\\n\" + e);\n                        }\n                    }\n                }\n\n                foreach (UrlDir urlDir in currentDir.children)\n                {\n                    WriteDirectoryRecursive(urlDir, Path.Combine(dirPath, urlDir.name));\n                }\n            }\n\n            try\n            {\n                WriteDirectoryRecursive(GameDatabase.Instance.root, path);\n            }\n            catch (DirectoryNotFoundException directoryNotFoundException)\n            {\n                Log(\"Exception while exporting the cfg\\n\" + directoryNotFoundException);\n            }\n            catch (IOException ioException)\n            {\n                Log(\"Exception while exporting the cfg\\n\" + ioException);\n            }\n            catch (UnauthorizedAccessException unauthorizedAccessException)\n            {\n                Log(\"Exception while exporting the cfg\\n\" + unauthorizedAccessException);\n            }\n        }\n\n        #endregion GUI stuff.\n\n        public bool ElectionAndCheck()\n        {\n            #region Type election\n\n            // TODO : Move the old version check in a process that call Update.\n\n            // Check for old version and MMSarbianExt\n            IEnumerable<AssemblyLoader.LoadedAssembly> oldMM =\n                AssemblyLoader.loadedAssemblies.Where(\n                    a => a.assembly.GetName().Name == Assembly.GetExecutingAssembly().GetName().Name)\n                    .Where(a => a.assembly.GetName().Version.CompareTo(new System.Version(1, 5, 0)) == -1);\n            IEnumerable<AssemblyLoader.LoadedAssembly> oldAssemblies =\n                oldMM.Concat(AssemblyLoader.loadedAssemblies.Where(a => a.assembly.GetName().Name == \"MMSarbianExt\"));\n            if (oldAssemblies.Any())\n            {\n                IEnumerable<string> badPaths =\n                    oldAssemblies.Select(a => a.path)\n                        .Select(\n                            p =>\n                                Uri.UnescapeDataString(\n                                    new Uri(Path.GetFullPath(KSPUtil.ApplicationRootPath)).MakeRelativeUri(new Uri(p))\n                                        .ToString()\n                                        .Replace('/', Path.DirectorySeparatorChar)));\n                string status =\n                    \"You have old versions of Module Manager (older than 1.5) or MMSarbianExt.\\nYou will need to remove them for Module Manager and the mods using it to work\\nExit KSP and delete those files :\\n\" +\n                    String.Join(\"\\n\", badPaths.ToArray());\n                PopupDialog.SpawnPopupDialog(new Vector2(0f, 1f), new Vector2(0f, 1f), \"ModuleManagerOldVersions\", \"Old versions of Module Manager\", status, \"OK\", false, UISkinManager.defaultSkin);\n                Log(\"Old version of Module Manager present. Stopping\");\n                return false;\n            }\n\n\n            //PopupDialog.SpawnPopupDialog(new Vector2(0.1f, 1f), new Vector2(0.2f, 1f), \"Test of the dialog\", \"Stuff\", \"OK\", false, UISkinManager.defaultSkin);\n\n            Assembly currentAssembly = Assembly.GetExecutingAssembly();\n            IEnumerable<AssemblyLoader.LoadedAssembly> eligible = from a in AssemblyLoader.loadedAssemblies\n                                                                  let ass = a.assembly\n                                                                  where ass.GetName().Name == currentAssembly.GetName().Name\n                                                                  orderby ass.GetName().Version descending, a.path ascending\n                                                                  select a;\n\n            // Elect the newest loaded version of MM to process all patch files.\n            // If there is a newer version loaded then don't do anything\n            // If there is a same version but earlier in the list, don't do anything either.\n            if (eligible.First().assembly != currentAssembly)\n            {\n                //loaded = true;\n                Log(\"version \" + currentAssembly.GetName().Version + \" at \" + currentAssembly.Location +\n                    \" lost the election\");\n                Destroy(gameObject);\n                return false;\n            }\n            string candidates = \"\";\n            foreach (AssemblyLoader.LoadedAssembly a in eligible)\n            {\n                if (currentAssembly.Location != a.path)\n                    candidates += \"Version \" + a.assembly.GetName().Version + \" \" + a.path + \" \" + \"\\n\";\n            }\n            if (candidates.Length > 0)\n            {\n                Log(\"version \" + currentAssembly.GetName().Version + \" at \" + currentAssembly.Location +\n                    \" won the election against\\n\" + candidates);\n            }\n\n            #endregion Type election\n\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/ModuleManager.csproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"12.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n    <ProductVersion>10.0.0</ProductVersion>\n    <SchemaVersion>2.0</SchemaVersion>\n    <ProjectGuid>{02C8E3AF-69F9-4102-AB60-DD6DE60662D3}</ProjectGuid>\n    <OutputType>Library</OutputType>\n    <RootNamespace>ModuleManager</RootNamespace>\n    <AssemblyName>ModuleManager</AssemblyName>\n    <TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>\n    <TargetFrameworkProfile />\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    <DebugSymbols>True</DebugSymbols>\n    <DebugType>full</DebugType>\n    <Optimize>False</Optimize>\n    <OutputPath>bin\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n    <ConsolePause>False</ConsolePause>\n    <LangVersion>default</LangVersion>\n    <Prefer32Bit>false</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    <DebugType>none</DebugType>\n    <Optimize>True</Optimize>\n    <OutputPath>bin\\Release\\</OutputPath>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n    <ConsolePause>False</ConsolePause>\n    <Prefer32Bit>false</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup>\n    <LangVersion>8.0</LangVersion>\n  </PropertyGroup>\n  <ItemGroup>\n    <Compile Include=\"Cats\\CatAnimator.cs\" />\n    <Compile Include=\"Cats\\CatManager.cs\" />\n    <Compile Include=\"Cats\\CatMover.cs\" />\n    <Compile Include=\"Cats\\CatOrbiter.cs\" />\n    <Compile Include=\"Collections\\ArrayEnumerator.cs\" />\n    <Compile Include=\"Collections\\ImmutableStack.cs\" />\n    <Compile Include=\"Collections\\KeyValueCache.cs\" />\n    <Compile Include=\"Collections\\MessageQueue.cs\" />\n    <Compile Include=\"Command.cs\" />\n    <Compile Include=\"CommandParser.cs\" />\n    <Compile Include=\"Extensions\\ByteArrayExtensions.cs\" />\n    <Compile Include=\"Extensions\\ConfigNodeExtensions.cs\" />\n    <Compile Include=\"Extensions\\IBasicLoggerExtensions.cs\" />\n    <Compile Include=\"Extensions\\NodeStackExtensions.cs\" />\n    <Compile Include=\"Extensions\\StringExtensions.cs\" />\n    <Compile Include=\"Extensions\\UrlConfigExtensions.cs\" />\n    <Compile Include=\"Extensions\\UrlDirExtensions.cs\" />\n    <Compile Include=\"Extensions\\UrlFileExtensions.cs\" />\n    <Compile Include=\"FatalErrorHandler.cs\" />\n    <Compile Include=\"FilePathRepository.cs\" />\n    <Compile Include=\"Fix16.cs\" />\n    <Compile Include=\"Logging\\IBasicLogger.cs\" />\n    <Compile Include=\"Logging\\ILogMessage.cs\" />\n    <Compile Include=\"Logging\\LogMessage.cs\" />\n    <Compile Include=\"Logging\\LogSplitter.cs\" />\n    <Compile Include=\"Logging\\PrefixLogger.cs\" />\n    <Compile Include=\"Logging\\QueueLogger.cs\" />\n    <Compile Include=\"Logging\\QueueLogRunner.cs\" />\n    <Compile Include=\"Logging\\StreamLogger.cs\" />\n    <Compile Include=\"Logging\\UnityLogger.cs\" />\n    <Compile Include=\"MMPatchLoader.cs\" />\n    <Compile Include=\"MMPatchRunner.cs\" />\n    <Compile Include=\"ModuleManager.cs\" />\n    <Compile Include=\"ModListGenerator.cs\" />\n    <Compile Include=\"PostPatchLoader.cs\" />\n    <Compile Include=\"ModuleManagerTestRunner.cs\" />\n    <Compile Include=\"NeedsChecker.cs\" />\n    <Compile Include=\"NodeMatcher.cs\" />\n    <Compile Include=\"Operator.cs\" />\n    <Compile Include=\"OperatorParser.cs\" />\n    <Compile Include=\"Pass.cs\" />\n    <Compile Include=\"PatchApplier.cs\" />\n    <Compile Include=\"PatchContext.cs\" />\n    <Compile Include=\"Patches\\CopyPatch.cs\" />\n    <Compile Include=\"Patches\\DeletePatch.cs\" />\n    <Compile Include=\"Patches\\EditPatch.cs\" />\n    <Compile Include=\"Patches\\InsertPatch.cs\" />\n    <Compile Include=\"Patches\\IPatch.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\AfterPassSpecifier.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\BeforePassSpecifier.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\FinalPassSpecifier.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\FirstPassSpecifier.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\ForPassSpecifier.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\InsertPassSpecifier.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\IPassSpecifier.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\LegacyPassSpecifier.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\LastPassSpecifier.cs\" />\n    <Compile Include=\"Patches\\PatchCompiler.cs\" />\n    <Compile Include=\"Patches\\ProtoPatch.cs\" />\n    <Compile Include=\"Patches\\ProtoPatchBuilder.cs\" />\n    <Compile Include=\"PatchExtractor.cs\" />\n    <Compile Include=\"PatchList.cs\" />\n    <Compile Include=\"Progress\\ProgressCounter.cs\" />\n    <Compile Include=\"Properties\\AssemblyInfo.cs\" />\n    <Compile Include=\"Properties\\Resources.Designer.cs\" />\n    <Compile Include=\"Progress\\IPatchProgress.cs\" />\n    <Compile Include=\"Progress\\PatchProgress.cs\" />\n    <Compile Include=\"ProtoUrlConfig.cs\" />\n    <Compile Include=\"Tags\\Tag.cs\" />\n    <Compile Include=\"Tags\\TagList.cs\" />\n    <Compile Include=\"Tags\\TagListParser.cs\" />\n    <Compile Include=\"Threading\\ITaskStatus.cs\" />\n    <Compile Include=\"Threading\\TaskStatus.cs\" />\n    <Compile Include=\"Threading\\TaskStatusWrapper.cs\" />\n    <Compile Include=\"ExceptionIntercept\\InterceptLogHandler.cs\" />\n    <Compile Include=\"Utils\\Counter.cs\" />\n    <Compile Include=\"Utils\\FileUtils.cs\" />\n    <Compile Include=\"CustomConfigsManager.cs\" />\n    <Compile Include=\"Threading\\BackgroundTask.cs\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Reference Include=\"Assembly-CSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL\">\n      <SpecificVersion>False</SpecificVersion>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"KSPAssets, Version=1.3.0.0, Culture=neutral, processorArchitecture=MSIL\">\n      <SpecificVersion>False</SpecificVersion>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\">\n    </Reference>\n    <Reference Include=\"UnityEngine, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL\">\n      <SpecificVersion>False</SpecificVersion>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL\">\n      <SpecificVersion>False</SpecificVersion>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"UnityEngine.ImageConversionModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL\">\n      <SpecificVersion>False</SpecificVersion>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"UnityEngine.InputLegacyModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL\">\n      <SpecificVersion>False</SpecificVersion>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"UnityEngine.Physics2DModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL\">\n      <SpecificVersion>False</SpecificVersion>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"UnityEngine.TextRenderingModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL\">\n      <SpecificVersion>False</SpecificVersion>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"UnityEngine.UI, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL\">\n      <SpecificVersion>False</SpecificVersion>\n      <Private>False</Private>\n    </Reference>\n    <Reference Include=\"UnityEngine.UIModule, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL\">\n      <SpecificVersion>False</SpecificVersion>\n      <Private>False</Private>\n    </Reference>\n  </ItemGroup>\n  <ItemGroup>\n    <EmbeddedResource Include=\"Properties\\Resources.resx\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"packages.config\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"Properties\\cat-1.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"Properties\\cat-10.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"Properties\\cat-11.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"Properties\\cat-12.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"Properties\\cat-2.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"Properties\\cat-3.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"Properties\\cat-4.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"Properties\\cat-5.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"Properties\\cat-6.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"Properties\\cat-7.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"Properties\\cat-8.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"Properties\\cat-9.png\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"Properties\\rainbow2.png\" />\n  </ItemGroup>\n  <ItemGroup />\n  <Import Project=\"$(MSBuildBinPath)\\Microsoft.CSharp.targets\" />\n  <PropertyGroup>\n    <PostBuildEvent>sh -c \"TARGET_PATH='$(TargetPath)' TARGET_DIR='$(TargetDir)' TARGET_NAME='$(TargetName)' sh '$(ProjectDir)/copy_build.sh'\"</PostBuildEvent>\n  </PropertyGroup>\n</Project>"
  },
  {
    "path": "ModuleManager/ModuleManagerTestRunner.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing ModuleManager.Extensions;\nusing ModuleManager.Logging;\n\nnamespace ModuleManager\n{\n    public class InGameTestRunner\n    {\n        private readonly IBasicLogger logger;\n\n        public InGameTestRunner(IBasicLogger logger)\n        {\n            this.logger = logger ?? throw new ArgumentNullException(nameof(logger));\n        }\n\n        public void RunTestCases(UrlDir gameDatabaseRoot)\n        {\n            if (gameDatabaseRoot == null) throw new ArgumentNullException(nameof(gameDatabaseRoot));\n            logger.Info(\"Running tests...\");\n\n            foreach (UrlDir.UrlConfig expect in gameDatabaseRoot.GetConfigs(\"MMTEST_EXPECT\"))\n            {\n                // So for each of the expects, we expect all the configs before that node to match exactly.\n                UrlDir.UrlFile parent = expect.parent;\n                if (parent.configs.Count != expect.config.CountNodes + 1)\n                {\n                    logger.Error(\"Test \" + parent.name + \" failed as expected number of nodes differs expected: \" +\n                        expect.config.CountNodes + \" found: \" + (parent.configs.Count - 1));\n                    for (int i = 0; i < parent.configs.Count; ++i)\n                        logger.Info(parent.configs[i].config.ToString());\n                    continue;\n                }\n                for (int i = 0; i < expect.config.CountNodes; ++i)\n                {\n                    ConfigNode gotNode = parent.configs[i].config;\n                    ConfigNode expectNode = expect.config.nodes[i];\n                    if (!CompareRecursive(expectNode, gotNode))\n                    {\n                        logger.Error(\"Test \" + parent.name + \"[\" + i +\n                            \"] failed as expected output and actual output differ.\\nexpected:\\n\" + expectNode +\n                            \"\\nActually got:\\n\" + gotNode);\n                    }\n                }\n\n                // Purge the tests\n                parent.configs.Clear();\n            }\n            logger.Info(\"tests complete.\");\n        }\n\n        private static bool CompareRecursive(ConfigNode expectNode, ConfigNode gotNode)\n        {\n            if (expectNode.values.Count != gotNode.values.Count || expectNode.nodes.Count != gotNode.nodes.Count)\n                return false;\n            for (int i = 0; i < expectNode.values.Count; ++i)\n            {\n                ConfigNode.Value eVal = expectNode.values[i];\n                ConfigNode.Value gVal = gotNode.values[i];\n                if (eVal.name != gVal.name || eVal.value != gVal.value)\n                    return false;\n            }\n            for (int i = 0; i < expectNode.nodes.Count; ++i)\n            {\n                ConfigNode eNode = expectNode.nodes[i];\n                ConfigNode gNode = gotNode.nodes[i];\n                if (!CompareRecursive(eNode, gNode))\n                    return false;\n            }\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/NeedsChecker.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics.CodeAnalysis;\nusing System.Linq;\nusing ModuleManager.Extensions;\nusing ModuleManager.Logging;\nusing ModuleManager.Progress;\nusing NodeStack = ModuleManager.Collections.ImmutableStack<ConfigNode>;\n\nnamespace ModuleManager\n{\n    public interface INeedsChecker\n    {\n        bool CheckNeeds(string mod);\n        bool CheckNeedsExpression(string needsString);\n        void CheckNeedsRecursive(ConfigNode node, UrlDir.UrlConfig urlConfig);\n    }\n\n    public class NeedsChecker : INeedsChecker\n    {\n        private readonly IEnumerable<string> mods;\n        private readonly UrlDir gameData;\n        private readonly IPatchProgress progress;\n        [SuppressMessage(\"CodeQuality\", \"IDE0052\", Justification = \"Reserved for future use\")]\n        private readonly IBasicLogger logger;\n\n        public NeedsChecker(IEnumerable<string> mods, UrlDir gameData, IPatchProgress progress, IBasicLogger logger)\n        {\n            this.mods = mods ?? throw new ArgumentNullException(nameof(mods));\n            this.gameData = gameData ?? throw new ArgumentNullException(nameof(gameData));\n            this.progress = progress ?? throw new ArgumentNullException(nameof(progress));\n            this.logger = logger ?? throw new ArgumentNullException(nameof(logger));\n        }\n\n        public bool CheckNeeds(string mod)\n        {\n            if (mod == null) throw new ArgumentNullException(nameof(mod));\n            if (mod == string.Empty) throw new ArgumentException(\"can't be empty\", nameof(mod));\n            return mods.Contains(mod, StringComparer.InvariantCultureIgnoreCase);\n        }\n\n        public bool CheckNeedsExpression(string needsExpression)\n        {\n            if (needsExpression == null) throw new ArgumentNullException(nameof(needsExpression));\n            if (needsExpression == string.Empty) throw new ArgumentException(\"can't be empty\", nameof(needsExpression));\n\n            foreach (string andDependencies in needsExpression.Split(',', '&'))\n            {\n                bool orMatch = false;\n                foreach (string orDependency in andDependencies.Split('|'))\n                {\n                    if (orDependency.Length == 0)\n                        continue;\n\n                    bool not = orDependency[0] == '!';\n                    string toFind = not ? orDependency.Substring(1) : orDependency;\n\n                    bool found = CheckNeedsWithDirectories(toFind);\n\n                    if (not == !found)\n                    {\n                        orMatch = true;\n                        break;\n                    }\n                }\n                if (!orMatch)\n                    return false;\n            }\n\n            return true;\n        }\n\n        public void CheckNeedsRecursive(ConfigNode node, UrlDir.UrlConfig urlConfig)\n        {\n            if (node == null) throw new ArgumentNullException(nameof(node));\n            if (urlConfig == null) throw new ArgumentNullException(nameof(urlConfig));\n            CheckNeedsRecursive(new NodeStack(node), urlConfig);\n        }\n\n        private bool CheckNeedsWithDirectories(string mod)\n        {\n            if (CheckNeeds(mod)) return true;\n            if (mod.Contains('/'))\n            {\n                string[] splits = mod.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);\n\n                bool result = true;\n                UrlDir current = gameData;\n                for (int i = 0; i < splits.Length; i++)\n                {\n                    current = current.children.FirstOrDefault(dir => dir.name == splits[i]);\n                    if (current == null)\n                    {\n                        result = false;\n                        break;\n                    }\n                }\n                return result;\n            }\n            return false;\n        }\n\n        private bool CheckNeedsName(ref string name)\n        {\n            if (name == null)\n                return true;\n\n            int idxStart = name.IndexOf(\":NEEDS[\", StringComparison.OrdinalIgnoreCase);\n            if (idxStart < 0)\n                return true;\n            int idxEnd = name.IndexOf(']', idxStart + 7);\n            string needsString = name.Substring(idxStart + 7, idxEnd - idxStart - 7);\n\n            name = name.Substring(0, idxStart) + name.Substring(idxEnd + 1);\n\n            return CheckNeedsExpression(needsString);\n        }\n\n        private void CheckNeedsRecursive(NodeStack nodeStack, UrlDir.UrlConfig urlConfig)\n        {\n            ConfigNode original = nodeStack.value;\n            for (int i = 0; i < original.values.Count; ++i)\n            {\n                ConfigNode.Value val = original.values[i];\n                string valname = val.name;\n                try\n                {\n                    if (CheckNeedsName(ref valname))\n                    {\n                        val.name = valname;\n                    }\n                    else\n                    {\n                        original.values.Remove(val);\n                        i--;\n                        progress.NeedsUnsatisfiedValue(urlConfig, nodeStack.GetPath() + '/' + val.name);\n                    }\n                }\n                catch (ArgumentOutOfRangeException e)\n                {\n                    progress.Exception(\"ArgumentOutOfRangeException in CheckNeeds for value \\\"\" + val.name + \"\\\"\", e);\n                    throw;\n                }\n                catch (Exception e)\n                {\n                    progress.Exception(\"General Exception in CheckNeeds for value \\\"\" + val.name + \"\\\"\", e);\n                    throw;\n                }\n            }\n\n            for (int i = 0; i < original.nodes.Count; ++i)\n            {\n                ConfigNode node = original.nodes[i];\n                string nodeName = node.name;\n\n                if (nodeName == null)\n                {\n                    progress.Error(urlConfig, \"Error - Node in file \" + urlConfig.SafeUrl() + \" subnode: \" + nodeStack.GetPath() + \" has config.name == null\");\n                }\n\n                try\n                {\n                    if (CheckNeedsName(ref nodeName))\n                    {\n                        node.name = nodeName;\n                        CheckNeedsRecursive(nodeStack.Push(node), urlConfig);\n                    }\n                    else\n                    {\n                        original.nodes.Remove(node);\n                        i--;\n                        progress.NeedsUnsatisfiedNode(urlConfig, nodeStack.GetPath() + '/' + node.name);\n                    }\n                }\n                catch (ArgumentOutOfRangeException e)\n                {\n                    progress.Exception(\"ArgumentOutOfRangeException in CheckNeeds for node \\\"\" + node.name + \"\\\"\", e);\n                    throw;\n                }\n                catch (Exception e)\n                {\n                    progress.Exception(\"General Exception \" + e.GetType().Name + \" for node \\\"\" + node.name + \"\\\"\", e);\n                    throw;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/NodeMatcher.cs",
    "content": "﻿using System;\nusing ModuleManager.Extensions;\n\nnamespace ModuleManager\n{\n    public interface INodeMatcher\n    {\n        bool IsMatch(ConfigNode node);\n    }\n\n    public class NodeMatcher : INodeMatcher\n    {\n        private readonly string type;\n        private readonly string[] namePatterns = null;\n        private readonly string constraints = \"\";\n\n        public NodeMatcher(string type, string name, string constraints)\n        {\n            if (type == string.Empty) throw new ArgumentException(\"can't be empty\", nameof(type));\n            this.type = type ?? throw new ArgumentNullException(nameof(type));\n\n            if (name == string.Empty) throw new ArgumentException(\"can't be empty (null allowed)\", nameof(name));\n            if (constraints == string.Empty) throw new ArgumentException(\"can't be empty (null allowed)\", nameof(constraints));\n\n            if (name != null) namePatterns = name.Split(',', '|');\n            if (constraints != null)\n            {\n                if (!constraints.IsBracketBalanced()) throw new ArgumentException(\"is not bracket balanced: \" + constraints, nameof(constraints));\n                this.constraints = constraints;\n            }\n        }\n\n        public bool IsMatch(ConfigNode node)\n        {\n            if (node.name != type) return false;\n\n            if (namePatterns != null)\n            {\n                string name = node.GetValue(\"name\");\n                if (name == null) return false;\n\n                bool match = false;\n                foreach (string pattern in namePatterns)\n                {\n                    if (MMPatchLoader.WildcardMatch(name, pattern))\n                    {\n                        match = true;\n                        break;\n                    }\n                }\n\n                if (!match) return false;\n            }\n\n            return MMPatchLoader.CheckConstraints(node, constraints);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Operator.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager\n{\n    public enum Operator\n    {\n        Assign,\n        Add,\n        Subtract,\n        Multiply,\n        Divide,\n        Exponentiate,\n        RegexReplace,\n    }\n}\n"
  },
  {
    "path": "ModuleManager/OperatorParser.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager\n{\n    public static class OperatorParser\n    {\n        public static Operator Parse(string name, out string valueName)\n        {\n            if (name == null) throw new ArgumentNullException(nameof(name));\n\n            if (name.Length == 0)\n            {\n                valueName = string.Empty;\n                return Operator.Assign;\n            }\n            else if (name.Length == 1 || (name[name.Length - 2] != ' ' && name[name.Length - 2] != '\\t'))\n            {\n                valueName = name;\n                return Operator.Assign;\n            }\n\n            Operator ret;\n            switch (name[name.Length - 1])\n            {\n                case '+':\n                    ret = Operator.Add;\n                    break;\n\n                case '-':\n                    ret = Operator.Subtract;\n                    break;\n\n                case '*':\n                    ret = Operator.Multiply;\n                    break;\n\n                case '/':\n                    ret = Operator.Divide;\n                    break;\n\n                case '!':\n                    ret = Operator.Exponentiate;\n                    break;\n\n                case '^':\n                    ret = Operator.RegexReplace;\n                    break;\n\n                default:\n                    valueName = name;\n                    return Operator.Assign;\n            }\n            valueName = name.Substring(0, name.Length - 1).TrimEnd();\n            return ret;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Pass.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing ModuleManager.Patches;\n\nnamespace ModuleManager\n{\n    public interface IPass : IEnumerable<IPatch>\n    {\n        string Name { get; }\n    }\n\n    public class Pass : IPass\n    {\n        private readonly string name;\n        private readonly List<IPatch> patches = new List<IPatch>(0);\n\n        public Pass(string name)\n        {\n            this.name = name ?? throw new ArgumentNullException(nameof(name));\n            if (name == string.Empty) throw new ArgumentException(\"can't be empty\", nameof(name));\n        }\n\n        public string Name => name;\n\n        public void Add(IPatch patch) => patches.Add(patch);\n\n        public List<IPatch>.Enumerator GetEnumerator() => patches.GetEnumerator();\n        IEnumerator<IPatch> IEnumerable<IPatch>.GetEnumerator() => GetEnumerator();\n        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();\n    }\n}\n"
  },
  {
    "path": "ModuleManager/PatchApplier.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing ModuleManager.Logging;\nusing ModuleManager.Extensions;\nusing ModuleManager.Patches;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager\n{\n    public class PatchApplier\n    {\n        private readonly IBasicLogger logger;\n        private readonly IPatchProgress progress;\n\n        public PatchApplier(IPatchProgress progress, IBasicLogger logger)\n        {\n            this.progress = progress ?? throw new ArgumentNullException(nameof(progress));\n            this.logger = logger ?? throw new ArgumentNullException(nameof(logger));\n        }\n\n        public IEnumerable<IProtoUrlConfig> ApplyPatches(IEnumerable<IPass> patches)\n        {\n            if (patches == null) throw new ArgumentNullException(nameof(patches));\n\n            LinkedList<IProtoUrlConfig> databaseConfigs = new LinkedList<IProtoUrlConfig>();\n\n            foreach (IPass pass in patches)\n            {\n                ApplyPatches(databaseConfigs, pass);\n            }\n\n            return databaseConfigs; \n        }\n\n        private void ApplyPatches(LinkedList<IProtoUrlConfig> databaseConfigs, IPass pass)\n        {\n            progress.PassStarted(pass);\n\n            foreach (IPatch patch in pass)\n            {\n                try\n                {\n                    patch.Apply(databaseConfigs, progress, logger);\n                    if (patch.CountsAsPatch) progress.PatchApplied();\n                }\n                catch (Exception e)\n                {\n                    progress.Exception(patch.UrlConfig, \"Exception while processing node : \" + patch.UrlConfig.SafeUrl(), e);\n                    logger.Error(\"Processed node was\\n\" + patch.UrlConfig.PrettyPrint());\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/PatchContext.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing ModuleManager.Logging;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager\n{\n    public struct PatchContext\n    {\n        public readonly UrlDir.UrlConfig patchUrl;\n        public readonly IEnumerable<IProtoUrlConfig> databaseConfigs;\n        public readonly IBasicLogger logger;\n        public readonly IPatchProgress progress;\n\n        public PatchContext(UrlDir.UrlConfig patchUrl, IEnumerable<IProtoUrlConfig> databaseConfigs, IBasicLogger logger, IPatchProgress progress)\n        {\n            this.patchUrl = patchUrl;\n            this.databaseConfigs = databaseConfigs;\n            this.logger = logger;\n            this.progress = progress;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/PatchExtractor.cs",
    "content": "﻿using System;\nusing System.Diagnostics.CodeAnalysis;\nusing ModuleManager.Extensions;\nusing ModuleManager.Logging;\nusing ModuleManager.Patches;\nusing ModuleManager.Progress;\nusing ModuleManager.Tags;\n\nnamespace ModuleManager\n{\n    public class PatchExtractor\n    {\n        private readonly IPatchProgress progress;\n        [SuppressMessage(\"CodeQuality\", \"IDE0052\", Justification = \"Reserved for future use\")]\n        private readonly IBasicLogger logger;\n        private readonly INeedsChecker needsChecker;\n        private readonly ITagListParser tagListParser;\n        private readonly IProtoPatchBuilder protoPatchBuilder;\n        private readonly IPatchCompiler patchCompiler;\n\n        public PatchExtractor(\n            IPatchProgress progress,\n            IBasicLogger logger,\n            INeedsChecker needsChecker,\n            ITagListParser tagListParser,\n            IProtoPatchBuilder protoPatchBuilder,\n            IPatchCompiler patchCompiler\n        )\n        {\n            this.progress = progress ?? throw new ArgumentNullException(nameof(progress));\n            this.logger = logger ?? throw new ArgumentNullException(nameof(logger));\n            this.needsChecker = needsChecker ?? throw new ArgumentNullException(nameof(needsChecker));\n            this.tagListParser = tagListParser ?? throw new ArgumentNullException(nameof(tagListParser));\n            this.protoPatchBuilder = protoPatchBuilder ?? throw new ArgumentNullException(nameof(protoPatchBuilder));\n            this.patchCompiler = patchCompiler ?? throw new ArgumentNullException(nameof(patchCompiler));\n        }\n\n        public IPatch ExtractPatch(UrlDir.UrlConfig urlConfig)\n        {\n            if (urlConfig == null) throw new ArgumentNullException(nameof(urlConfig));\n\n            try\n            {\n                if (!urlConfig.type.IsBracketBalanced())\n                {\n                    progress.Error(urlConfig, \"Error - node name does not have balanced brackets (or a space - if so replace with ?):\\n\" + urlConfig.SafeUrl());\n                    return null;\n                }\n\n                Command command = CommandParser.Parse(urlConfig.type, out string name);\n                \n                if (command == Command.Replace)\n                {\n                    progress.Error(urlConfig, $\"Error - replace command (%) is not valid on a root node: {urlConfig.SafeUrl()}\");\n                    return null;\n                }\n                else if (command == Command.Create)\n                {\n                    progress.Error(urlConfig, $\"Error - create command (&) is not valid on a root node: {urlConfig.SafeUrl()}\");\n                    return null;\n                }\n                else if (command == Command.Rename)\n                {\n                    progress.Error(urlConfig, $\"Error - rename command (|) is not valid on a root node: {urlConfig.SafeUrl()}\");\n                    return null;\n                }\n                else if (command == Command.Paste)\n                {\n                    progress.Error(urlConfig, $\"Error - paste command (#) is not valid on a root node: {urlConfig.SafeUrl()}\");\n                    return null;\n                }\n                else if (command == Command.Special)\n                {\n                    progress.Error(urlConfig, $\"Error - special command (*) is not valid on a root node: {urlConfig.SafeUrl()}\");\n                    return null;\n                }\n\n                ITagList tagList;\n                try\n                {\n                    tagList = tagListParser.Parse(name, urlConfig);\n                }\n                catch (FormatException ex)\n                {\n                    progress.Error(urlConfig, $\"Cannot parse node name as tag list: {ex.Message}\\non: {urlConfig.SafeUrl()}\");\n                    return null;\n                }\n\n                ProtoPatch protoPatch = protoPatchBuilder.Build(urlConfig, command, tagList);\n\n                if (protoPatch == null)\n                {\n                    return null;\n                }\n\n                if (protoPatch.needs != null && !needsChecker.CheckNeedsExpression(protoPatch.needs))\n                {\n                    progress.NeedsUnsatisfiedRoot(urlConfig);\n                    return null;\n                }\n                else if (!protoPatch.passSpecifier.CheckNeeds(needsChecker, progress))\n                {\n                    return null;\n                }\n\n                needsChecker.CheckNeedsRecursive(urlConfig.config, urlConfig);\n                return patchCompiler.CompilePatch(protoPatch);\n            }\n            catch(Exception e)\n            {\n                progress.Exception(urlConfig, $\"Exception while attempting to create patch from config: {urlConfig.SafeUrl()}\", e);\n                return null;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/PatchList.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing ModuleManager.Patches;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager\n{\n    public class PatchList : IEnumerable<IPass>\n    {\n        private class ModPass\n        {\n            public readonly string name;\n            public readonly Pass beforePass;\n            public readonly Pass forPass;\n            public readonly Pass afterPass;\n            public readonly Pass lastPass;\n\n            public ModPass(string name)\n            {\n                if (name == null) throw new ArgumentNullException(nameof(name));\n                if (name == string.Empty) throw new ArgumentException(\"can't be blank\", nameof(name));\n                this.name = name.ToUpperInvariant();\n\n                beforePass = new Pass($\":BEFORE[{this.name}]\");\n                forPass = new Pass($\":FOR[{this.name}]\");\n                afterPass = new Pass($\":AFTER[{this.name}]\");\n                lastPass = new Pass($\":LAST[{this.name}]\");\n            }\n\n            public void AddBeforePatch(IPatch patch) => beforePass.Add(patch ?? throw new ArgumentNullException(nameof(patch)));\n            public void AddForPatch(IPatch patch) => forPass.Add(patch ?? throw new ArgumentNullException(nameof(patch)));\n            public void AddAfterPatch(IPatch patch) => afterPass.Add(patch ?? throw new ArgumentNullException(nameof(patch)));\n            public void AddLastPatch(IPatch patch) => lastPass.Add(patch ?? throw new ArgumentNullException(nameof(patch)));\n        }\n\n        private readonly Pass insertPatches = new Pass(\":INSERT (initial)\");\n        private readonly Pass firstPatches = new Pass(\":FIRST\");\n        private readonly Pass legacyPatches = new Pass(\":LEGACY (default)\");\n        private readonly Pass finalPatches = new Pass(\":FINAL\");\n\n        private readonly SortedDictionary<string, ModPass> modPasses = new SortedDictionary<string, ModPass>(StringComparer.InvariantCultureIgnoreCase);\n        private readonly SortedDictionary<string, Pass> lastPasses = new SortedDictionary<string, Pass>(StringComparer.InvariantCultureIgnoreCase);\n\n        public PatchList(IEnumerable<string> modList, IEnumerable<IPatch> patches, IPatchProgress progress)\n        {\n            if (modList == null) throw new ArgumentNullException(nameof(modList));\n            if (patches == null) throw new ArgumentNullException(nameof(patches));\n            if (progress == null) throw new ArgumentNullException(nameof(progress));\n\n            foreach (string mod in modList)\n            {\n                modPasses.Add(mod, new ModPass(mod));\n            }\n\n            foreach (IPatch patch in patches)\n            {\n                if (patch.PassSpecifier is InsertPassSpecifier)\n                {\n                    insertPatches.Add(patch);\n                }\n                else if (patch.PassSpecifier is FirstPassSpecifier)\n                {\n                    firstPatches.Add(patch);\n                }\n                else if (patch.PassSpecifier is LegacyPassSpecifier)\n                {\n                    legacyPatches.Add(patch);\n                }\n                else if (patch.PassSpecifier is BeforePassSpecifier beforePassSpecifier)\n                {\n                    EnsureMod(beforePassSpecifier.mod);\n                    modPasses[beforePassSpecifier.mod].AddBeforePatch(patch);\n                }\n                else if (patch.PassSpecifier is ForPassSpecifier forPassSpecifier)\n                {\n                    EnsureMod(forPassSpecifier.mod);\n                    modPasses[forPassSpecifier.mod].AddForPatch(patch);\n                }\n                else if (patch.PassSpecifier is AfterPassSpecifier afterPassSpecifier)\n                {\n                    EnsureMod(afterPassSpecifier.mod);\n                    modPasses[afterPassSpecifier.mod].AddAfterPatch(patch);\n                }\n                else if (patch.PassSpecifier is LastPassSpecifier lastPassSpecifier)\n                {\n                    if (!lastPasses.TryGetValue(lastPassSpecifier.mod, out Pass thisPass))\n                    {\n                        thisPass = new Pass($\":LAST[{lastPassSpecifier.mod.ToUpperInvariant()}]\");\n                        lastPasses.Add(lastPassSpecifier.mod.ToLowerInvariant(), thisPass);\n                    }\n                    thisPass.Add(patch);\n                }\n                else if (patch.PassSpecifier is FinalPassSpecifier)\n                {\n                    finalPatches.Add(patch);\n                }\n                else\n                {\n                    throw new NotImplementedException(\"Don't know what to do with pass specifier: \" + patch.PassSpecifier.Descriptor);\n                }\n\n                if (patch.CountsAsPatch) progress.PatchAdded();\n            }\n        }\n\n        public IEnumerator<IPass> GetEnumerator()\n        {\n            yield return insertPatches;\n            yield return firstPatches;\n            yield return legacyPatches;\n\n            foreach (ModPass modPass in modPasses.Values)\n            {\n                yield return modPass.beforePass;\n                yield return modPass.forPass;\n                yield return modPass.afterPass;\n            }\n\n            foreach (Pass lastPass in lastPasses.Values)\n            {\n                yield return lastPass;\n            }\n\n            yield return finalPatches;\n        }\n\n        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();\n\n        private void EnsureMod(string mod)\n        {\n            if (mod == null) throw new ArgumentNullException(nameof(mod));\n            if (mod == string.Empty) throw new ArgumentException(\"can't be empty\", nameof(mod));\n            if (!modPasses.ContainsKey(mod)) throw new KeyNotFoundException($\"Mod '{mod}' not found\");\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/CopyPatch.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing NodeStack = ModuleManager.Collections.ImmutableStack<ConfigNode>;\nusing ModuleManager.Extensions;\nusing ModuleManager.Logging;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Patches\n{\n    public class CopyPatch : IPatch\n    {\n        public UrlDir.UrlConfig UrlConfig { get; }\n        public INodeMatcher NodeMatcher { get; }\n        public IPassSpecifier PassSpecifier { get; }\n        public bool CountsAsPatch => true;\n\n        public CopyPatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher, IPassSpecifier passSpecifier)\n        {\n            UrlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig));\n            NodeMatcher = nodeMatcher ?? throw new ArgumentNullException(nameof(nodeMatcher));\n            PassSpecifier = passSpecifier ?? throw new ArgumentNullException(nameof(passSpecifier));\n        }\n\n        public void Apply(LinkedList<IProtoUrlConfig> databaseConfigs, IPatchProgress progress, IBasicLogger logger)\n        {\n            if (databaseConfigs == null) throw new ArgumentNullException(nameof(databaseConfigs));\n            if (progress == null) throw new ArgumentNullException(nameof(progress));\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n\n            PatchContext context = new PatchContext(UrlConfig, databaseConfigs, logger, progress);\n\n            for (LinkedListNode<IProtoUrlConfig> listNode = databaseConfigs.First; listNode != null; listNode = listNode.Next)\n            {\n                IProtoUrlConfig protoConfig = listNode.Value;\n                try\n                {\n                    if (!NodeMatcher.IsMatch(protoConfig.Node)) continue;\n\n                    ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(protoConfig.Node), UrlConfig.config, context);\n                    if (protoConfig.Node.GetValue(\"name\") is string name && name == clone.GetValue(\"name\"))\n                    {\n                        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)\");\n                    }\n                    else\n                    {\n                        progress.ApplyingCopy(protoConfig, UrlConfig);\n                        listNode = databaseConfigs.AddAfter(listNode, new ProtoUrlConfig(protoConfig.UrlFile, clone));\n                    }\n                }\n                catch (Exception ex)\n                {\n                    progress.Exception(UrlConfig, $\"Exception while applying copy {UrlConfig.SafeUrl()} to {protoConfig.FullUrl}\", ex);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/DeletePatch.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing ModuleManager.Extensions;\nusing ModuleManager.Logging;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Patches\n{\n    public class DeletePatch : IPatch\n    {\n        public UrlDir.UrlConfig UrlConfig { get; }\n        public INodeMatcher NodeMatcher { get; }\n        public IPassSpecifier PassSpecifier { get; }\n        public bool CountsAsPatch => true;\n\n        public DeletePatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher, IPassSpecifier passSpecifier)\n        {\n            UrlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig));\n            NodeMatcher = nodeMatcher ?? throw new ArgumentNullException(nameof(nodeMatcher));\n            PassSpecifier = passSpecifier ?? throw new ArgumentNullException(nameof(passSpecifier));\n        }\n\n        public void Apply(LinkedList<IProtoUrlConfig> databaseConfigs, IPatchProgress progress, IBasicLogger logger)\n        {\n            if (databaseConfigs == null) throw new ArgumentNullException(nameof(databaseConfigs));\n            if (progress == null) throw new ArgumentNullException(nameof(progress));\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n\n            LinkedListNode<IProtoUrlConfig> currentNode = databaseConfigs.First;\n            while (currentNode != null)\n            {\n                IProtoUrlConfig protoConfig = currentNode.Value;\n                try\n                {\n                    LinkedListNode<IProtoUrlConfig> nextNode = currentNode.Next;\n                    if (NodeMatcher.IsMatch(protoConfig.Node))\n                    {\n                        progress.ApplyingDelete(protoConfig, UrlConfig);\n                        databaseConfigs.Remove(currentNode);\n                    }\n                    currentNode = nextNode;\n                }\n                catch (Exception ex)\n                {\n                    progress.Exception(UrlConfig, $\"Exception while applying delete {UrlConfig.SafeUrl()} to {protoConfig.FullUrl}\", ex);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/EditPatch.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing NodeStack = ModuleManager.Collections.ImmutableStack<ConfigNode>;\nusing ModuleManager.Extensions;\nusing ModuleManager.Logging;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Patches\n{\n    public class EditPatch : IPatch\n    {\n        private readonly bool loop;\n\n        public UrlDir.UrlConfig UrlConfig { get; }\n        public INodeMatcher NodeMatcher { get; }\n        public IPassSpecifier PassSpecifier { get; }\n        public bool CountsAsPatch => true;\n\n        public EditPatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher, IPassSpecifier passSpecifier)\n        {\n            UrlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig));\n            NodeMatcher = nodeMatcher ?? throw new ArgumentNullException(nameof(nodeMatcher));\n            PassSpecifier = passSpecifier ?? throw new ArgumentNullException(nameof(passSpecifier));\n\n            loop = urlConfig.config.HasNode(\"MM_PATCH_LOOP\");\n        }\n\n        public void Apply(LinkedList<IProtoUrlConfig> databaseConfigs, IPatchProgress progress, IBasicLogger logger)\n        {\n            if (databaseConfigs == null) throw new ArgumentNullException(nameof(databaseConfigs));\n            if (progress == null) throw new ArgumentNullException(nameof(progress));\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n\n            PatchContext context = new PatchContext(UrlConfig, databaseConfigs, logger, progress);\n            for (LinkedListNode<IProtoUrlConfig> listNode = databaseConfigs.First; listNode != null; listNode = listNode.Next)\n            {\n                IProtoUrlConfig protoConfig = listNode.Value;\n                try\n                {\n                    if (!NodeMatcher.IsMatch(protoConfig.Node)) continue;\n                    if (loop) logger.Info($\"Looping on {UrlConfig.SafeUrl()} to {protoConfig.FullUrl}\");\n\n                    do\n                    {\n                        progress.ApplyingUpdate(protoConfig, UrlConfig);\n                        listNode.Value = protoConfig = new ProtoUrlConfig(protoConfig.UrlFile, MMPatchLoader.ModifyNode(new NodeStack(protoConfig.Node), UrlConfig.config, context));\n                    } while (loop && NodeMatcher.IsMatch(protoConfig.Node));\n\n                    if (loop) protoConfig.Node.RemoveNodes(\"MM_PATCH_LOOP\");\n                }\n                catch (Exception ex)\n                {\n                    progress.Exception(UrlConfig, $\"Exception while applying update {UrlConfig.SafeUrl()} to {protoConfig.FullUrl}\", ex);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/IPatch.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing ModuleManager.Logging;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Patches\n{\n    public interface IPatch\n    {\n        UrlDir.UrlConfig UrlConfig { get; }\n        IPassSpecifier PassSpecifier { get; }\n        bool CountsAsPatch { get; }\n        void Apply(LinkedList<IProtoUrlConfig> configs, IPatchProgress progress, IBasicLogger logger);\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/InsertPatch.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing ModuleManager.Extensions;\nusing ModuleManager.Logging;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Patches\n{\n    public class InsertPatch : IPatch\n    {\n        public UrlDir.UrlConfig UrlConfig { get; }\n        public string NodeType { get; }\n        public IPassSpecifier PassSpecifier { get; }\n        public bool CountsAsPatch => false;\n\n        public InsertPatch(UrlDir.UrlConfig urlConfig, string nodeType, IPassSpecifier passSpecifier)\n        {\n            UrlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig));\n            NodeType = nodeType ?? throw new ArgumentNullException(nameof(nodeType));\n            PassSpecifier = passSpecifier ?? throw new ArgumentNullException(nameof(passSpecifier));\n        }\n\n        public void Apply(LinkedList<IProtoUrlConfig> configs, IPatchProgress progress, IBasicLogger logger)\n        {\n            if (configs == null) throw new ArgumentNullException(nameof(configs));\n            if (progress == null) throw new ArgumentNullException(nameof(progress));\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n\n            ConfigNode node = UrlConfig.config.DeepCopy();\n            node.name = NodeType;\n            configs.AddLast(new ProtoUrlConfig(UrlConfig.parent, node));\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/PassSpecifiers/AfterPassSpecifier.cs",
    "content": "﻿using System;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Patches.PassSpecifiers\n{\n    public class AfterPassSpecifier : IPassSpecifier\n    {\n        public readonly string mod;\n        public readonly UrlDir.UrlConfig urlConfig;\n\n        public AfterPassSpecifier(string mod, UrlDir.UrlConfig urlConfig)\n        {\n            if (mod == string.Empty) throw new ArgumentException(\"can't be empty\", nameof(mod));\n            this.mod = mod ?? throw new ArgumentNullException(nameof(mod));\n            this.urlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig));\n        }\n\n        public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress)\n        {\n            if (needsChecker == null) throw new ArgumentNullException(nameof(needsChecker));\n            if (progress == null) throw new ArgumentNullException(nameof(progress));\n            bool result = needsChecker.CheckNeeds(mod);\n            if (!result) progress.NeedsUnsatisfiedAfter(urlConfig);\n            return result;\n        }\n\n        public string Descriptor => $\":AFTER[{mod.ToUpper()}]\";\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/PassSpecifiers/BeforePassSpecifier.cs",
    "content": "﻿using System;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Patches.PassSpecifiers\n{\n    public class BeforePassSpecifier : IPassSpecifier\n    {\n        public readonly string mod;\n        public readonly UrlDir.UrlConfig urlConfig;\n\n        public BeforePassSpecifier(string mod, UrlDir.UrlConfig urlConfig)\n        {\n            if (mod == string.Empty) throw new ArgumentException(\"can't be empty\", nameof(mod));\n            this.mod = mod ?? throw new ArgumentNullException(nameof(mod));\n            this.urlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig));\n        }\n\n        public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress)\n        {\n            if (needsChecker == null) throw new ArgumentNullException(nameof(needsChecker));\n            if (progress == null) throw new ArgumentNullException(nameof(progress));\n            bool result = needsChecker.CheckNeeds(mod);\n            if (!result) progress.NeedsUnsatisfiedBefore(urlConfig);\n            return result;\n        }\n\n        public string Descriptor => $\":BEFORE[{mod.ToUpper()}]\";\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/PassSpecifiers/FinalPassSpecifier.cs",
    "content": "﻿using System;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Patches.PassSpecifiers\n{\n    public class FinalPassSpecifier : IPassSpecifier\n    {\n        public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress)\n        {\n            if (needsChecker == null) throw new ArgumentNullException(nameof(needsChecker));\n            if (progress == null) throw new ArgumentNullException(nameof(progress));\n            return true;\n        }\n\n        public string Descriptor => \":FINAL\";\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/PassSpecifiers/FirstPassSpecifier.cs",
    "content": "﻿using System;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Patches.PassSpecifiers\n{\n    public class FirstPassSpecifier : IPassSpecifier\n    {\n        public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress)\n        {\n            if (needsChecker == null) throw new ArgumentNullException(nameof(needsChecker));\n            if (progress == null) throw new ArgumentNullException(nameof(progress));\n            return true;\n        }\n\n        public string Descriptor => \":FIRST\";\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/PassSpecifiers/ForPassSpecifier.cs",
    "content": "﻿using System;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Patches.PassSpecifiers\n{\n    public class ForPassSpecifier : IPassSpecifier\n    {\n        public readonly string mod;\n        public readonly UrlDir.UrlConfig urlConfig;\n\n        public ForPassSpecifier(string mod, UrlDir.UrlConfig urlConfig)\n        {\n            if (mod == string.Empty) throw new ArgumentException(\"can't be empty\", nameof(mod));\n            this.mod = mod ?? throw new ArgumentNullException(nameof(mod));\n            this.urlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig));\n        }\n\n        public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress)\n        {\n            if (needsChecker == null) throw new ArgumentNullException(nameof(needsChecker));\n            if (progress == null) throw new ArgumentNullException(nameof(progress));\n            bool result = needsChecker.CheckNeeds(mod);\n            if (!result) progress.NeedsUnsatisfiedFor(urlConfig);\n            return result;\n        }\n\n        public string Descriptor => $\":FOR[{mod.ToUpper()}]\";\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/PassSpecifiers/IPassSpecifier.cs",
    "content": "﻿using System;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Patches.PassSpecifiers\n{\n    public interface IPassSpecifier\n    {\n        bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress);\n        string Descriptor { get; }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/PassSpecifiers/InsertPassSpecifier.cs",
    "content": "﻿using System;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Patches.PassSpecifiers\n{\n    public class InsertPassSpecifier : IPassSpecifier\n    {\n        public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress)\n        {\n            if (needsChecker == null) throw new ArgumentNullException(nameof(needsChecker));\n            if (progress == null) throw new ArgumentNullException(nameof(progress));\n            return true;\n        }\n\n        public string Descriptor => \":INSERT (initial)\";\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/PassSpecifiers/LastPassSpecifier.cs",
    "content": "using System;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Patches.PassSpecifiers\n{\n    public class LastPassSpecifier : IPassSpecifier\n    {\n        public readonly string mod;\n\n        public LastPassSpecifier(string mod)\n        {\n            if (mod == string.Empty) throw new ArgumentException(\"can't be empty\", nameof(mod));\n            this.mod = mod ?? throw new ArgumentNullException(nameof(mod));\n        }\n\n        public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress) => true;\n        public string Descriptor => $\":LAST[{mod.ToUpper()}]\";\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/PassSpecifiers/LegacyPassSpecifier.cs",
    "content": "﻿using System;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Patches.PassSpecifiers\n{\n    public class LegacyPassSpecifier : IPassSpecifier\n    {\n        public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress)\n        {\n            if (needsChecker == null) throw new ArgumentNullException(nameof(needsChecker));\n            if (progress == null) throw new ArgumentNullException(nameof(progress));\n            return true;\n        }\n\n        public string Descriptor => \":LEGACY (default)\";\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/PatchCompiler.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager.Patches\n{\n    public interface IPatchCompiler\n    {\n        IPatch CompilePatch(ProtoPatch protoPatch);\n    }\n\n    public class PatchCompiler : IPatchCompiler\n    {\n        public IPatch CompilePatch(ProtoPatch protoPatch)\n        {\n            if (protoPatch == null) throw new ArgumentNullException(nameof(protoPatch));\n\n            return protoPatch.command switch\n            {\n                Command.Insert => new InsertPatch(protoPatch.urlConfig, protoPatch.nodeType, protoPatch.passSpecifier),\n                Command.Edit => new EditPatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier),\n                Command.Copy => new CopyPatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier),\n                Command.Delete => new DeletePatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier),\n                _ => throw new ArgumentException(\"has an invalid command for a root node: \" + protoPatch.command, nameof(protoPatch)),\n            };\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/ProtoPatch.cs",
    "content": "﻿using System;\nusing ModuleManager.Patches.PassSpecifiers;\n\nnamespace ModuleManager.Patches\n{\n    public class ProtoPatch\n    {\n        public readonly UrlDir.UrlConfig urlConfig;\n        public readonly Command command;\n        public readonly string nodeType;\n        public readonly string nodeName;\n        public readonly string needs = null;\n        public readonly string has = null;\n        public readonly IPassSpecifier passSpecifier;\n\n        public ProtoPatch(UrlDir.UrlConfig urlConfig, Command command, string nodeType, string nodeName, string needs, string has, IPassSpecifier passSpecifier)\n        {\n            this.urlConfig = urlConfig;\n            this.command = command;\n            this.nodeType = nodeType;\n            this.nodeName = nodeName;\n            this.needs = needs;\n            this.has = has;\n            this.passSpecifier = passSpecifier;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Patches/ProtoPatchBuilder.cs",
    "content": "﻿using System;\nusing ModuleManager.Extensions;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\nusing ModuleManager.Tags;\n\nnamespace ModuleManager.Patches\n{\n    public interface IProtoPatchBuilder\n    {\n        ProtoPatch Build(UrlDir.UrlConfig urlConfig, Command command, ITagList tagList);\n    }\n\n    public class ProtoPatchBuilder : IProtoPatchBuilder\n    {\n        private readonly IPatchProgress progress;\n\n        public ProtoPatchBuilder(IPatchProgress progress)\n        {\n            this.progress = progress ?? throw new ArgumentNullException(nameof(progress));\n        }\n\n        public ProtoPatch Build(UrlDir.UrlConfig urlConfig, Command command, ITagList tagList)\n        {\n            if (urlConfig == null) throw new ArgumentNullException(nameof(urlConfig));\n            if (tagList == null) throw new ArgumentNullException(nameof(tagList));\n            if (progress == null) throw new ArgumentNullException(nameof(progress));\n\n            bool error = false;\n\n            string nodeType = tagList.PrimaryTag.key;\n            string nodeName = tagList.PrimaryTag.value;\n\n            if (command == Command.Insert && nodeName != null)\n            {\n                progress.Error(urlConfig, \"name specifier detected on insert node (not a patch): \" + urlConfig.SafeUrl());\n                error = true;\n            }\n\n            if (nodeName == string.Empty)\n            {\n                progress.Warning(urlConfig, \"empty brackets detected on patch name: \" + urlConfig.SafeUrl());\n                nodeName = null;\n            }\n\n            if (tagList.PrimaryTag.trailer != null)\n                progress.Warning(urlConfig, \"unrecognized trailer: '\" + tagList.PrimaryTag.trailer + \"' on: \" + urlConfig.SafeUrl());\n\n            string needs = null;\n            string has = null;\n            IPassSpecifier passSpecifier = null;\n\n            foreach (Tag tag in tagList)\n            {\n                if (tag.trailer != null)\n                    progress.Warning(urlConfig, \"unrecognized trailer: '\" + tag.trailer + \"' on: \" + urlConfig.SafeUrl());\n\n                if (tag.key.Equals(\"NEEDS\", StringComparison.CurrentCultureIgnoreCase))\n                {\n                    if (needs != null)\n                    {\n                        progress.Warning(urlConfig, \"more than one :NEEDS tag detected, ignoring all but the first: \" + urlConfig.SafeUrl());\n                        continue;\n                    }\n                    if (string.IsNullOrEmpty(tag.value))\n                    {\n                        progress.Error(urlConfig, \"empty :NEEDS tag detected: \" + urlConfig.SafeUrl());\n                        error = true;\n                        continue;\n                    }\n\n                    needs = tag.value;\n                }\n                else if (tag.key.Equals(\"HAS\", StringComparison.CurrentCultureIgnoreCase))\n                {\n                    if (command == Command.Insert)\n                    {\n                        progress.Error(urlConfig, \":HAS detected on insert node (not a patch): \" + urlConfig.SafeUrl());\n                        error = true;\n                        continue;\n                    }\n                    if (has != null)\n                    {\n                        progress.Warning(urlConfig, \"more than one :HAS tag detected, ignoring all but the first: \" + urlConfig.SafeUrl());\n                        continue;\n                    }\n                    if (string.IsNullOrEmpty(tag.value))\n                    {\n                        progress.Error(urlConfig, \"empty :HAS tag detected: \" + urlConfig.SafeUrl());\n                        error = true;\n                        continue;\n                    }\n\n                    has = tag.value;\n                }\n                else if (tag.key.Equals(\"FIRST\", StringComparison.CurrentCultureIgnoreCase))\n                {\n                    if (tag.value != null)\n                    {\n                        progress.Warning(urlConfig, \"value detected on :FIRST tag: \" + urlConfig.SafeUrl());\n                    }\n\n                    if (command == Command.Insert)\n                    {\n                        progress.Error(urlConfig, \"pass specifier detected on insert node (not a patch): \" + urlConfig.SafeUrl());\n                        error = true;\n                        continue;\n                    }\n                    if (passSpecifier != null)\n                    {\n                        progress.Warning(urlConfig, \"more than one pass specifier detected, ignoring all but the first: \" + urlConfig.SafeUrl());\n                        continue;\n                    }\n\n                    passSpecifier = new FirstPassSpecifier();\n                }\n                else if (tag.key.Equals(\"BEFORE\", StringComparison.CurrentCultureIgnoreCase))\n                {\n                    if (string.IsNullOrEmpty(tag.value))\n                    {\n                        progress.Error(urlConfig, \"empty :BEFORE tag detected: \" + urlConfig.SafeUrl());\n                        error = true;\n                        continue;\n                    }\n\n                    if (command == Command.Insert)\n                    {\n                        progress.Error(urlConfig, \"pass specifier detected on insert node (not a patch): \" + urlConfig.SafeUrl());\n                        error = true;\n                        continue;\n                    }\n                    if (passSpecifier != null)\n                    {\n                        progress.Warning(urlConfig, \"more than one pass specifier detected, ignoring all but the first: \" + urlConfig.SafeUrl());\n                        continue;\n                    }\n\n                    passSpecifier = new BeforePassSpecifier(tag.value, urlConfig);\n                }\n                else if (tag.key.Equals(\"FOR\", StringComparison.CurrentCultureIgnoreCase))\n                {\n                    if (string.IsNullOrEmpty(tag.value))\n                    {\n                        progress.Error(urlConfig, \"empty :FOR tag detected: \" + urlConfig.SafeUrl());\n                        error = true;\n                        continue;\n                    }\n\n                    if (command == Command.Insert)\n                    {\n                        progress.Error(urlConfig, \"pass specifier detected on insert node (not a patch): \" + urlConfig.SafeUrl());\n                        error = true;\n                        continue;\n                    }\n                    if (passSpecifier != null)\n                    {\n                        progress.Warning(urlConfig, \"more than one pass specifier detected, ignoring all but the first: \" + urlConfig.SafeUrl());\n                        continue;\n                    }\n\n                    passSpecifier = new ForPassSpecifier(tag.value, urlConfig);\n                }\n                else if (tag.key.Equals(\"AFTER\", StringComparison.CurrentCultureIgnoreCase))\n                {\n                    if (string.IsNullOrEmpty(tag.value))\n                    {\n                        progress.Error(urlConfig, \"empty :AFTER tag detected: \" + urlConfig.SafeUrl());\n                        error = true;\n                        continue;\n                    }\n\n                    if (command == Command.Insert)\n                    {\n                        progress.Error(urlConfig, \"pass specifier detected on insert node (not a patch): \" + urlConfig.SafeUrl());\n                        error = true;\n                        continue;\n                    }\n                    if (passSpecifier != null)\n                    {\n                        progress.Warning(urlConfig, \"more than one pass specifier detected, ignoring all but the first: \" + urlConfig.SafeUrl());\n                        continue;\n                    }\n\n                    passSpecifier = new AfterPassSpecifier(tag.value, urlConfig);\n                }\n                else if (tag.key.Equals(\"LAST\", StringComparison.CurrentCultureIgnoreCase))\n                {\n                    if (string.IsNullOrEmpty(tag.value))\n                    {\n                        progress.Error(urlConfig, \"empty :LAST tag detected: \" + urlConfig.SafeUrl());\n                        error = true;\n                        continue;\n                    }\n\n                    if (command == Command.Insert)\n                    {\n                        progress.Error(urlConfig, \"pass specifier detected on insert node (not a patch): \" + urlConfig.SafeUrl());\n                        error = true;\n                        continue;\n                    }\n                    if (passSpecifier != null)\n                    {\n                        progress.Warning(urlConfig, \"more than one pass specifier detected, ignoring all but the first: \" + urlConfig.SafeUrl());\n                        continue;\n                    }\n\n                    passSpecifier = new LastPassSpecifier(tag.value);\n                }\n                else if (tag.key.Equals(\"FINAL\", StringComparison.CurrentCultureIgnoreCase))\n                {\n                    if (tag.value != null)\n                    {\n                        progress.Warning(urlConfig, \"value detected on :FINAL tag: \" + urlConfig.SafeUrl());\n                    }\n\n                    if (command == Command.Insert)\n                    {\n                        progress.Error(urlConfig, \"pass specifier detected on insert node (not a patch): \" + urlConfig.SafeUrl());\n                        error = true;\n                        continue;\n                    }\n                    if (passSpecifier != null)\n                    {\n                        progress.Warning(urlConfig, \"more than one pass specifier detected, ignoring all but the first: \" + urlConfig.SafeUrl());\n                        continue;\n                    }\n\n                    passSpecifier = new FinalPassSpecifier();\n                }\n                else\n                {\n                    progress.Warning(urlConfig, \"unrecognized tag: '\" + tag.key + \"' on: \" + urlConfig.SafeUrl());\n                }\n            }\n\n            if (error) return null;\n\n            if (passSpecifier == null)\n            {\n                if (command == Command.Insert) passSpecifier = new InsertPassSpecifier();\n                else passSpecifier = new LegacyPassSpecifier();\n            }\n\n            return new ProtoPatch(urlConfig, command, nodeType, nodeName, needs, has, passSpecifier);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/PostPatchLoader.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Reflection;\nusing UnityEngine;\nusing ModuleManager.Extensions;\nusing ModuleManager.Logging;\n\nusing static ModuleManager.FilePathRepository;\nusing System.Diagnostics.CodeAnalysis;\n\nnamespace ModuleManager\n{\n    public delegate void ModuleManagerPostPatchCallback();\n\n    public class PostPatchLoader : LoadingSystem\n    {\n        public static PostPatchLoader Instance { get; private set; }\n\n        public IEnumerable<IProtoUrlConfig> databaseConfigs = null;\n\n        private static readonly List<ModuleManagerPostPatchCallback> postPatchCallbacks = new List<ModuleManagerPostPatchCallback>();\n\n        private readonly IBasicLogger logger = new PrefixLogger(\"ModuleManager\", new UnityLogger(UnityEngine.Debug.unityLogger));\n\n        private bool ready = false;\n\n        private string progressTitle = \"ModuleManager: Post patch\";\n\n        public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback)\n        {\n            if (!postPatchCallbacks.Contains(callback))\n                postPatchCallbacks.Add(callback);\n        }\n\n        [SuppressMessage(\"CodeQuality\", \"IDE0051\", Justification = \"Called by Unity\")]\n        private void Awake()\n        {\n            if (Instance != null)\n            {\n                Destroy(this);\n                return;\n            }\n            Instance = this;\n        }\n\n        public override bool IsReady() => ready;\n\n        public override float LoadWeight() => 0;\n\n        public override float ProgressFraction() => 1;\n\n        public override string ProgressTitle() => progressTitle;\n\n        public override void StartLoad()\n        {\n            ready = false;\n            StartCoroutine(Run());\n        }\n\n        private IEnumerator Run()\n        {\n            Stopwatch waitTimer = new Stopwatch();\n            waitTimer.Start();\n\n            progressTitle = \"ModuleManager: Waiting for patching to finish\";\n\n            while (databaseConfigs == null) yield return null;\n\n            waitTimer.Stop();\n            logger.Info(\"Waited \" + ((float)waitTimer.ElapsedMilliseconds / 1000).ToString(\"F3\") + \"s for patching to finish\");\n\n            Stopwatch postPatchTimer = new Stopwatch();\n            postPatchTimer.Start();\n\n            progressTitle = \"ModuleManager: Applying patched game database\";\n            logger.Info(\"Applying patched game database\");\n\n            foreach (UrlDir.UrlFile file in GameDatabase.Instance.root.AllConfigFiles)\n            {\n                file.configs.Clear();\n            }\n\n            foreach (IProtoUrlConfig protoConfig in databaseConfigs)\n            {\n                protoConfig.UrlFile.AddConfig(protoConfig.Node);\n            }\n\n            databaseConfigs = null;\n\n            yield return null;\n\n            if (File.Exists(logPath))\n            {\n                if (ModuleManager.DontCopyLogs)\n                {\n                    logger.Info(\"Not dumping log because -mm-dont-copy-logs was set\");\n                }\n                else\n                {\n                    progressTitle = \"ModuleManager: Dumping log to KSP log\";\n                    logger.Info(\"Dumping ModuleManager log to main log\");\n                    logger.Info(\"\\n#### BEGIN MODULEMANAGER LOG ####\\n\\n\\n\" + File.ReadAllText(logPath) + \"\\n\\n\\n#### END MODULEMANAGER LOG ####\");\n                }\n            }\n            else\n            {\n                logger.Error(\"ModuleManager log does not exist: \" + logPath);\n            }\n\n            yield return null;\n\n#if DEBUG\n            InGameTestRunner testRunner = new InGameTestRunner(logger);\n            testRunner.RunTestCases(GameDatabase.Instance.root);\n#endif\n\n            yield return null;\n\n            progressTitle = \"ModuleManager: Reloading things\";\n\n            logger.Info(\"Reloading resources definitions\");\n            PartResourceLibrary.Instance.LoadDefinitions();\n\n            logger.Info(\"Reloading Trait configs\");\n            GameDatabase.Instance.ExperienceConfigs.LoadTraitConfigs();\n\n            logger.Info(\"Reloading Part Upgrades\");\n            PartUpgradeManager.Handler.FillUpgrades();\n\n            LoadModdedPhysics();\n\n            yield return null;\n\n            progressTitle = \"ModuleManager: Running post patch callbacks\";\n            logger.Info(\"Running post patch callbacks\");\n\n            foreach (ModuleManagerPostPatchCallback callback in postPatchCallbacks)\n            {\n                try\n                {\n                    callback();\n                }\n                catch (Exception e)\n                {\n                    logger.Exception(\"Exception while running a post patch callback\", e);\n                }\n                yield return null;\n            }\n            yield return null;\n\n            // Call all \"public static void ModuleManagerPostLoad()\" on all class\n            foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies())\n            {\n                try\n                {\n                    foreach (Type type in ass.GetTypes())\n                    {\n                        MethodInfo method = type.GetMethod(\"ModuleManagerPostLoad\", BindingFlags.Public | BindingFlags.Static);\n\n                        if (method != null && method.GetParameters().Length == 0)\n                        {\n                            try\n                            {\n                                logger.Info(\"Calling \" + ass.GetName().Name + \".\" + type.Name + \".\" + method.Name + \"()\");\n                                method.Invoke(null, null);\n                            }\n                            catch (Exception e)\n                            {\n                                logger.Exception(\"Exception while calling \" + ass.GetName().Name + \".\" + type.Name + \".\" + method.Name + \"()\", e);\n                            }\n                        }\n                    }\n                }\n                catch (Exception e)\n                {\n                    logger.Exception(\"Post run call threw an exception in loading \" + ass.FullName, e);\n                }\n            }\n\n            yield return null;\n\n            // Call \"public void ModuleManagerPostLoad()\" on all active MonoBehaviour instance\n            foreach (MonoBehaviour obj in FindObjectsOfType<MonoBehaviour>())\n            {\n                MethodInfo method = obj.GetType().GetMethod(\"ModuleManagerPostLoad\", BindingFlags.Public | BindingFlags.Instance);\n\n                if (method != null && method.GetParameters().Length == 0)\n                {\n                    try\n                    {\n                        logger.Info(\"Calling \" + obj.GetType().Name + \".\" + method.Name + \"()\");\n                        method.Invoke(obj, null);\n                    }\n                    catch (Exception e)\n                    {\n                        logger.Exception(\"Exception while calling \" + obj.GetType().Name + \".\" + method.Name + \"() :\\n\", e);\n                    }\n                }\n            }\n\n            yield return null;\n\n            if (ModuleManager.dumpPostPatch)\n                ModuleManager.OutputAllConfigs();\n\n            postPatchTimer.Stop();\n            logger.Info(\"Post patch ran in \" + ((float)postPatchTimer.ElapsedMilliseconds / 1000).ToString(\"F3\") + \"s\");\n\n            ready = true;\n        }\n\n        private void LoadModdedPhysics()\n        {\n            if (!File.Exists(physicsPath))\n            {\n                logger.Error(\"Physics file not found\");\n                return;\n            }\n\n            logger.Info(\"Setting modded physics as the active one\");\n\n            PhysicsGlobals.PhysicsDatabaseFilename = physicsPath;\n\n            if (!PhysicsGlobals.Instance.LoadDatabase())\n                logger.Error(\"Something went wrong while setting the active physics config.\");\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Progress/IPatchProgress.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager.Progress\n{\n    public interface IPatchProgress\n    {\n        ProgressCounter Counter { get; }\n\n        float ProgressFraction { get; }\n\n        EventVoid OnPatchApplied { get; }\n        EventData<IPass> OnPassStarted { get; }\n\n        void Warning(UrlDir.UrlConfig url, string message);\n        void Error(UrlDir.UrlConfig url, string message);\n        void Error(string message);\n        void Exception(string message, Exception exception);\n        void Exception(UrlDir.UrlConfig url, string message, Exception exception);\n        void NeedsUnsatisfiedRoot(UrlDir.UrlConfig url);\n        void NeedsUnsatisfiedNode(UrlDir.UrlConfig url, string path);\n        void NeedsUnsatisfiedValue(UrlDir.UrlConfig url, string path);\n        void NeedsUnsatisfiedBefore(UrlDir.UrlConfig url);\n        void NeedsUnsatisfiedFor(UrlDir.UrlConfig url);\n        void NeedsUnsatisfiedAfter(UrlDir.UrlConfig url);\n        void ApplyingCopy(IUrlConfigIdentifier original, UrlDir.UrlConfig patch);\n        void ApplyingDelete(IUrlConfigIdentifier original, UrlDir.UrlConfig patch);\n        void ApplyingUpdate(IUrlConfigIdentifier original, UrlDir.UrlConfig patch);\n        void PatchAdded();\n        void PatchApplied();\n        void PassStarted(IPass pass);\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Progress/PatchProgress.cs",
    "content": "﻿using System;\nusing ModuleManager.Extensions;\nusing ModuleManager.Logging;\n\nnamespace ModuleManager.Progress\n{\n    public class PatchProgress : IPatchProgress\n    {\n        public ProgressCounter Counter { get; private set; }\n\n        private readonly IBasicLogger logger;\n\n        public float ProgressFraction\n        {\n            get\n            {\n                if (Counter.totalPatches > 0)\n                    return Counter.appliedPatches / (float)Counter.totalPatches;\n                return 0;\n            }\n        }\n\n        public EventVoid OnPatchApplied { get; } = new EventVoid(\"OnPatchApplied\");\n        public EventData<IPass> OnPassStarted { get; } = new EventData<IPass>(\"OnPassStarted\");\n\n        public PatchProgress(IBasicLogger logger)\n        {\n            this.logger = logger;\n            Counter = new ProgressCounter();\n        }\n\n        public PatchProgress(IPatchProgress progress, IBasicLogger logger)\n        {\n            this.logger = logger;\n            Counter = progress.Counter;\n        }\n\n        public void PatchAdded()\n        {\n            Counter.totalPatches.Increment();\n        }\n\n        public void PassStarted(IPass pass)\n        {\n            if (pass == null) throw new ArgumentNullException(nameof(pass));\n            logger.Info(pass.Name + \" pass\");\n            OnPassStarted.Fire(pass);\n        }\n\n        public void ApplyingUpdate(IUrlConfigIdentifier original, UrlDir.UrlConfig patch)\n        {\n            logger.Info($\"Applying update {patch.SafeUrl()} to {original.FullUrl}\");\n            Counter.patchedNodes.Increment();\n        }\n\n        public void ApplyingCopy(IUrlConfigIdentifier original, UrlDir.UrlConfig patch)\n        {\n            logger.Info($\"Applying copy {patch.SafeUrl()} to {original.FullUrl}\");\n            Counter.patchedNodes.Increment();\n        }\n\n        public void ApplyingDelete(IUrlConfigIdentifier original, UrlDir.UrlConfig patch)\n        {\n            logger.Info($\"Applying delete {patch.SafeUrl()} to {original.FullUrl}\");\n            Counter.patchedNodes.Increment();\n        }\n\n        public void PatchApplied()\n        {\n            Counter.appliedPatches.Increment();\n            OnPatchApplied.Fire();\n        }\n\n        public void NeedsUnsatisfiedRoot(UrlDir.UrlConfig url)\n        {\n            logger.Info($\"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its NEEDS\");\n            Counter.needsUnsatisfied.Increment();\n        }\n\n        public void NeedsUnsatisfiedNode(UrlDir.UrlConfig url, string path)\n        {\n            logger.Info($\"Deleting node in file {url.parent.url} subnode: {path} as it can't satisfy its NEEDS\");\n        }\n\n        public void NeedsUnsatisfiedValue(UrlDir.UrlConfig url, string path)\n        {\n            logger.Info($\"Deleting value in file {url.parent.url} value: {path} as it can't satisfy its NEEDS\");\n        }\n\n        public void NeedsUnsatisfiedBefore(UrlDir.UrlConfig url)\n        {\n            logger.Info($\"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its BEFORE\");\n            Counter.needsUnsatisfied.Increment();\n        }\n\n        public void NeedsUnsatisfiedFor(UrlDir.UrlConfig url)\n        {\n            logger.Warning($\"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its FOR (this shouldn't happen)\");\n            Counter.needsUnsatisfied.Increment();\n        }\n\n        public void NeedsUnsatisfiedAfter(UrlDir.UrlConfig url)\n        {\n            logger.Info($\"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its AFTER\");\n            Counter.needsUnsatisfied.Increment();\n        }\n\n        public void Warning(UrlDir.UrlConfig url, string message)\n        {\n            Counter.warnings.Increment();\n            logger.Warning(message);\n            RecordWarningFile(url);\n        }\n\n        public void Error(UrlDir.UrlConfig url, string message)\n        {\n            Counter.errors.Increment();\n            logger.Error(message);\n            RecordErrorFile(url);\n        }\n\n        public void Error(string message)\n        {\n            Counter.errors.Increment();\n            logger.Error(message);\n        }\n\n        public void Exception(string message, Exception exception)\n        {\n            Counter.exceptions.Increment();\n            logger.Exception(message, exception);\n        }\n\n        public void Exception(UrlDir.UrlConfig url, string message, Exception exception)\n        {\n            Exception(message, exception);\n            RecordErrorFile(url);\n        }\n\n        private void RecordWarningFile(UrlDir.UrlConfig url)\n        {\n            string key = url.parent.GetUrlWithExtension();\n            if (key[0] == '/')\n                key = key.Substring(1);\n\n            if (Counter.warningFiles.ContainsKey(key))\n                Counter.warningFiles[key] += 1;\n            else\n                Counter.warningFiles[key] = 1;\n        }\n\n        private void RecordErrorFile(UrlDir.UrlConfig url)\n        {\n            string key = url.parent.GetUrlWithExtension();\n            if (key[0] == '/')\n                key = key.Substring(1);\n\n            if (Counter.errorFiles.ContainsKey(key))\n                Counter.errorFiles[key] += 1;\n            else\n                Counter.errorFiles[key] = 1;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Progress/ProgressCounter.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing ModuleManager.Utils;\n\nnamespace ModuleManager.Progress\n{\n    public class ProgressCounter\n    {\n        public readonly Counter totalPatches = new Counter();\n        public readonly Counter appliedPatches = new Counter();\n        public readonly Counter patchedNodes = new Counter();\n        public readonly Counter warnings = new Counter();\n        public readonly Counter errors = new Counter();\n        public readonly Counter exceptions = new Counter();\n        public readonly Counter needsUnsatisfied = new Counter();\n\n        public readonly Dictionary<String, int> warningFiles = new Dictionary<string, int>();\n        public readonly Dictionary<String, int> errorFiles = new Dictionary<string, int>();\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Properties/AssemblyInfo.cs",
    "content": "using System.Reflection;\nusing System.Runtime.CompilerServices;\n\n// Information about this assembly is defined by the following attributes. \n// Change them to the values specific to your project.\n\n[assembly: AssemblyTitle(\"ModuleManager\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"\")]\n[assembly: AssemblyCopyright(\"\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// The assembly version has the format \"{Major}.{Minor}.{Build}.{Revision}\".\n// The form \"{Major}.{Minor}.*\" will automatically update the build and revision,\n// and \"{Major}.{Minor}.{Build}.*\" will update just the revision.\n\n[assembly: AssemblyVersion(\"4.2.3\")]\n[assembly: KSPAssembly(\"ModuleManager\", 2, 5)]\n\n// The following attributes are used to specify the signing key for the assembly, \n// if desired. See the Mono documentation for more information about signing.\n\n//[assembly: AssemblyDelaySign(false)]\n//[assembly: AssemblyKeyFile(\"\")]\n\n"
  },
  {
    "path": "ModuleManager/Properties/Resources.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code was generated by a tool.\n//     Runtime Version:4.0.30319.34209\n//\n//     Changes to this file may cause incorrect behavior and will be lost if\n//     the code is regenerated.\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace ModuleManager.Properties {\n    using System;\n    \n    \n    /// <summary>\n    ///   A strongly-typed resource class, for looking up localized strings, etc.\n    /// </summary>\n    // This class was auto-generated by the StronglyTypedResourceBuilder\n    // class via a tool like ResGen or Visual Studio.\n    // To add or remove a member, edit your .ResX file then rerun ResGen\n    // with the /str option, or rebuild your VS project.\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"4.0.0.0\")]\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    internal class Resources {\n        \n        private static global::System.Resources.ResourceManager resourceMan;\n        \n        private static global::System.Globalization.CultureInfo resourceCulture;\n        \n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\n        internal Resources() {\n        }\n        \n        /// <summary>\n        ///   Returns the cached ResourceManager instance used by this class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Resources.ResourceManager ResourceManager {\n            get {\n                if (object.ReferenceEquals(resourceMan, null)) {\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"ModuleManager.Properties.Resources\", typeof(Resources).Assembly);\n                    resourceMan = temp;\n                }\n                return resourceMan;\n            }\n        }\n        \n        /// <summary>\n        ///   Overrides the current thread's CurrentUICulture property for all\n        ///   resource lookups using this strongly typed resource class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Globalization.CultureInfo Culture {\n            get {\n                return resourceCulture;\n            }\n            set {\n                resourceCulture = value;\n            }\n        }\n        \n        /// <summary>\n        ///   Looks up a localized resource of type System.Byte[].\n        /// </summary>\n        internal static byte[] cat1 {\n            get {\n                object obj = ResourceManager.GetObject(\"cat-1\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n\n        internal static byte[] cat2\n        {\n            get\n            {\n                object obj = ResourceManager.GetObject(\"cat-2\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n\n        internal static byte[] cat3\n        {\n            get\n            {\n                object obj = ResourceManager.GetObject(\"cat-3\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n        internal static byte[] cat4\n        {\n            get\n            {\n                object obj = ResourceManager.GetObject(\"cat-4\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n        internal static byte[] cat5\n        {\n            get\n            {\n                object obj = ResourceManager.GetObject(\"cat-5\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n        internal static byte[] cat6\n        {\n            get\n            {\n                object obj = ResourceManager.GetObject(\"cat-6\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n        internal static byte[] cat7\n        {\n            get\n            {\n                object obj = ResourceManager.GetObject(\"cat-7\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n        internal static byte[] cat8\n        {\n            get\n            {\n                object obj = ResourceManager.GetObject(\"cat-8\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n        internal static byte[] cat9\n        {\n            get\n            {\n                object obj = ResourceManager.GetObject(\"cat-9\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n        internal static byte[] cat10\n        {\n            get\n            {\n                object obj = ResourceManager.GetObject(\"cat-10\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n        internal static byte[] cat11\n        {\n            get\n            {\n                object obj = ResourceManager.GetObject(\"cat-11\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n        internal static byte[] cat12\n        {\n            get\n            {\n                object obj = ResourceManager.GetObject(\"cat-12\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n\n        internal static byte[] rainbow\n        {\n            get\n            {\n                object obj = ResourceManager.GetObject(\"rainbow2\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Properties/Resources.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <assembly alias=\"System.Windows.Forms\" name=\"System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" />\n  <data name=\"cat-1\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>cat-1.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n  <data name=\"cat-10\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>cat-10.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n  <data name=\"cat-11\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>cat-11.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n  <data name=\"cat-12\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>cat-12.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n  <data name=\"cat-2\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>cat-2.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n  <data name=\"cat-3\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>cat-3.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n  <data name=\"cat-4\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>cat-4.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n  <data name=\"cat-5\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>cat-5.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n  <data name=\"cat-6\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>cat-6.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n  <data name=\"cat-7\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>cat-7.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n  <data name=\"cat-8\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>cat-8.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n  <data name=\"cat-9\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>cat-9.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n  <data name=\"rainbow2\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>rainbow2.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n</root>"
  },
  {
    "path": "ModuleManager/ProtoUrlConfig.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager\n{\n    public interface IUrlConfigIdentifier\n    {\n        string FileUrl { get; }\n        string NodeType { get; }\n        string FullUrl { get; }\n    }\n\n    public interface IProtoUrlConfig : IUrlConfigIdentifier\n    {\n        UrlDir.UrlFile UrlFile { get; }\n        ConfigNode Node { get; }\n    }\n\n    public class ProtoUrlConfig : IProtoUrlConfig\n    {\n        public UrlDir.UrlFile UrlFile { get; }\n        public ConfigNode Node { get; }\n        public string FileUrl { get; }\n        public string NodeType => Node.name;\n        public string FullUrl { get; }\n\n        public ProtoUrlConfig(UrlDir.UrlFile urlFile, ConfigNode node)\n        {\n            UrlFile = urlFile ?? throw new ArgumentNullException(nameof(urlFile));\n            Node = node ?? throw new ArgumentNullException(nameof(node));\n            FileUrl = UrlFile.url + '.' + urlFile.fileExtension;\n            FullUrl = FileUrl + '/' + Node.name;\n\n            if (node.GetValue(\"name\") is string nameValue)\n                FullUrl += '[' + nameValue + ']';\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Tags/Tag.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager.Tags\n{\n    public struct Tag\n    {\n        public readonly string key;\n        public readonly string value;\n        public readonly string trailer;\n\n        public Tag(string key, string value, string trailer)\n        {\n            this.key = key ?? throw new ArgumentNullException(nameof(key));\n            if (key == string.Empty) throw new ArgumentException(\"can't be empty\", nameof(key));\n\n            if (value == null && trailer != null)\n                throw new ArgumentException(\"trailer must be null if value is null\");\n\n            if (trailer == string.Empty) throw new ArgumentException(\"can't be empty (null allowed)\", nameof(trailer));\n\n            this.value = value;\n            this.trailer = trailer;\n        }\n\n        public override string ToString()\n        {\n            string s = \"< '\" + key + \"' \";\n            if (value != null) s += \"[ '\" + value + \"' ] \";\n            if (trailer != null) s += \"'\" + trailer + \"' \";\n            s += \">\";\n            return s;\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Tags/TagList.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Linq;\nusing ModuleManager.Collections;\n\nnamespace ModuleManager.Tags\n{\n    public interface ITagList : IEnumerable<Tag>\n    {\n        Tag PrimaryTag { get; }\n    }\n\n    public class TagList : ITagList\n    {\n        private readonly Tag[] tags;\n\n        public TagList(Tag primaryTag, IEnumerable<Tag> tags)\n        {\n            PrimaryTag = primaryTag;\n            this.tags = tags?.ToArray() ?? throw new ArgumentNullException(nameof(tags));\n        }\n        \n        public Tag PrimaryTag { get; private set; }\n\n        public ArrayEnumerator<Tag> GetEnumerator() => new ArrayEnumerator<Tag>(tags);\n        IEnumerator<Tag> IEnumerable<Tag>.GetEnumerator() => GetEnumerator();\n        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Tags/TagListParser.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing ModuleManager.Progress;\n\nnamespace ModuleManager.Tags\n{\n    public interface ITagListParser\n    {\n        ITagList Parse(string ToParse, UrlDir.UrlConfig urlConfig);\n    }\n\n    public class TagListParser : ITagListParser\n    {\n        private readonly IPatchProgress progress;\n\n        public TagListParser(IPatchProgress progress)\n        {\n            this.progress = progress ?? throw new ArgumentNullException(nameof(progress));\n        }\n\n        public ITagList Parse(string toParse, UrlDir.UrlConfig urlConfig)\n        {\n            if (toParse == null) throw new ArgumentNullException(nameof(toParse));\n            if (urlConfig == null) throw new ArgumentNullException(nameof(urlConfig));\n            if (toParse.Length == 0) throw new FormatException(\"can't create tag list from empty string\");\n            if (toParse[0] == '[') throw new FormatException(\"can't create tag list beginning with [\");\n            if (toParse[0] == ':') throw new FormatException(\"can't create tag list beginning with :\");\n\n            if (toParse[toParse.Length - 1] == ':')\n            {\n                progress.Warning(urlConfig, \"trailing : detected\");\n                toParse = toParse.TrimEnd(':');\n            }\n\n            List<Tag> tags = new List<Tag>();\n            Tag primaryTag = ParsePrimaryTag(toParse, ref tags, urlConfig);\n            return new TagList(primaryTag, tags);\n        }\n\n        private Tag ParsePrimaryTag(string toParse, ref List<Tag> tags, UrlDir.UrlConfig urlConfig)\n        {\n            for (int i = 1; i < toParse.Length; i++)\n            {\n                char c = toParse[i];\n\n                if (c == '[')\n                {\n                    int j = ClosingBracketIndex(toParse, i + 1);\n                    return ParsePrimaryTrailer(toParse, j + 1, ref tags, toParse.Substring(0, i), toParse.Substring(i + 1, j - i - 1), urlConfig);\n                }\n                else if (c == ':')\n                {\n                    ParseTag(toParse, i + 1, ref tags, urlConfig);\n                    return new Tag(toParse.Substring(0, i), null, null);\n                }\n                else if (c == ']')\n                {\n                    throw new FormatException(\"encountered closing bracket in primary key\");\n                }\n            }\n\n            return new Tag(toParse, null, null);\n        }\n\n        private Tag ParsePrimaryTrailer(string toParse, int start, ref List<Tag> tags, string primaryKey, string primaryValue, UrlDir.UrlConfig urlConfig)\n        {\n            for (int i = start; i < toParse.Length; i++)\n            {\n                char c = toParse[i];\n\n                if (c == ':')\n                {\n                    string trailer = i == start ? null : toParse.Substring(start, i - start);\n                    ParseTag(toParse, i + 1, ref tags, urlConfig);\n                    return new Tag(primaryKey, primaryValue, trailer);\n                }\n                else if (c == '[')\n                {\n                    throw new FormatException(\"encountered opening bracket in primary trailer\");\n                }\n                else if (c == ']')\n                {\n                    throw new FormatException(\"encountered closing bracket in primary trailer\");\n                }\n            }\n            \n            string primaryTrailer = toParse.Length - start == 0 ? null : toParse.Substring(start);\n            return new Tag(primaryKey, primaryValue, primaryTrailer);\n        }\n\n        private void ParseTag(string toParse, int start, ref List<Tag> tags, UrlDir.UrlConfig urlConfig)\n        {\n            for (int i = start; i < toParse.Length; i++)\n            {\n                char c = toParse[i];\n\n                if (c == '[')\n                {\n                    if (i == start)\n                        throw new FormatException(\"tag can't start with [\");\n\n                    int j = ClosingBracketIndex(toParse, i + 1);\n                    ParseTrailer(toParse, j + 1, ref tags, toParse.Substring(start, i - start), toParse.Substring(i + 1, j - i - 1), urlConfig);\n                    return;\n                }\n                else if (c == ':')\n                {\n                    if (i == start)\n                        progress.Warning(urlConfig, \"extra : detected\");\n                    else\n                        tags.Add(new Tag(toParse.Substring(start, i - start), null, null));\n\n                    ParseTag(toParse, i + 1, ref tags, urlConfig);\n                    return;\n                }\n                else if (c == ']')\n                {\n                    throw new FormatException(\"encountered closing bracket in key\");\n                }\n            }\n\n            tags.Add(new Tag(toParse.Substring(start), null, null));\n        }\n\n        private void ParseTrailer(string toParse, int start, ref List<Tag> tags, string key, string value, UrlDir.UrlConfig urlConfig)\n        {\n            for (int i = start; i < toParse.Length; i++)\n            {\n                char c = toParse[i];\n\n                if (c == ':')\n                {\n                    string trailer = i == start ? null : toParse.Substring(start, i - start);\n                    tags.Add(new Tag(key, value, trailer));\n                    ParseTag(toParse, i + 1, ref tags, urlConfig);\n                    return;\n                }\n                else if (c == '[')\n                {\n                    throw new FormatException(\"encountered opening bracket in trailer\");\n                }\n                else if (c == ']')\n                {\n                    throw new FormatException(\"encountered closing bracket in trailer\");\n                }\n            }\n\n            string finalTrailer = toParse.Length - start == 0 ? null : toParse.Substring(start);\n            tags.Add(new Tag(key, value, finalTrailer));\n        }\n\n        private static int ClosingBracketIndex(string toParse, int start)\n        {\n            int bracketLevel = 0;\n\n            for (int i = start; i < toParse.Length; i++)\n            {\n                char c = toParse[i];\n\n                if (c == '[')\n                {\n                    bracketLevel++;\n                }\n                else if (c == ']')\n                {\n                    bracketLevel--;\n                }\n\n                if (bracketLevel == -1) return i;\n            }\n\n            throw new FormatException(\"reached end of the tag list without encountering a close bracket\");\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Threading/BackgroundTask.cs",
    "content": "﻿using System;\nusing System.Threading;\n\nnamespace ModuleManager.Threading\n{\n    public static class BackgroundTask\n    {\n        public static ITaskStatus Start(Action action)\n        {\n            if (action == null) throw new ArgumentNullException(nameof(action));\n\n            TaskStatus status = new TaskStatus();\n\n            void RunAction()\n            {\n                try\n                {\n                    action();\n                    status.Finished();\n                }\n                catch (Exception ex)\n                {\n                    status.Error(ex);\n                }\n            }\n\n            Thread thread = new Thread(RunAction);\n            thread.Start();\n\n            return new TaskStatusWrapper(status);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Threading/ITaskStatus.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager.Threading\n{\n    public interface ITaskStatus\n    {\n        bool IsRunning { get; }\n        bool IsFinished { get; }\n        bool IsExitedWithError { get; }\n        Exception Exception { get; }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Threading/TaskStatus.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager.Threading\n{\n    public class TaskStatus : ITaskStatus\n    {\n        private readonly object lockObject = new object();\n\n        public bool IsRunning { get; private set; } = true;\n        public Exception Exception { get; private set; } = null;\n\n        public bool IsFinished\n        {\n            get\n            {\n                lock (lockObject)\n                {\n                    return !IsRunning && Exception == null;\n                }\n            }\n        }\n\n        public bool IsExitedWithError\n        {\n            get\n            {\n                lock (lockObject)\n                {\n                    return !IsRunning && Exception != null;\n                }\n            }\n        }\n\n        public void Finished()\n        {\n            lock (lockObject)\n            {\n                if (!IsRunning) throw new InvalidOperationException(\"Task is not running\");\n                IsRunning = false;\n            }\n        }\n\n        public void Error(Exception exception)\n        {\n            lock(lockObject)\n            {\n                if (!IsRunning) throw new InvalidOperationException(\"Task is not running\");\n                this.Exception = exception ?? throw new ArgumentNullException(nameof(exception));\n                IsRunning = false;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Threading/TaskStatusWrapper.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager.Threading\n{\n    public class TaskStatusWrapper : ITaskStatus\n    {\n        private readonly ITaskStatus inner;\n\n        public TaskStatusWrapper(ITaskStatus inner)\n        {\n            this.inner = inner;\n        }\n\n        public bool IsRunning => inner.IsRunning;\n        public bool IsFinished => inner.IsFinished;\n        public bool IsExitedWithError => inner.IsExitedWithError;\n        public Exception Exception => inner.Exception;\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Utils/Counter.cs",
    "content": "﻿using System;\n\nnamespace ModuleManager.Utils\n{\n    public class Counter\n    {\n        public int Value { get; private set; } = 0;\n\n        public void Increment()\n        {\n            Value++;\n        }\n\n        public override string ToString()\n        {\n            return Value.ToString();\n        }\n\n        public static implicit operator int(Counter counter) => counter.Value;\n    }\n}\n"
  },
  {
    "path": "ModuleManager/Utils/FileUtils.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Security.Cryptography;\nusing ModuleManager.Extensions;\n\nnamespace ModuleManager.Utils\n{\n    public static class FileUtils\n    {\n        public static string FileSHA(string filename)\n        {\n            if (!File.Exists(filename)) throw new FileNotFoundException(\"File does not exist\", filename);\n\n            using SHA256 sha = SHA256.Create();\n            using FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);\n            byte[] data = sha.ComputeHash(fs);\n\n            return data.ToHex();\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManager/copy_build.sh",
    "content": "if [ -z \"${TARGET_PATH}\" ] ; then\n  echo 'Expected $TARGET_PATH to be defined but it is not' >&2\n  exit 1\nelif ! [ -f \"${TARGET_PATH}\" ] ; then\n  echo 'Expected $TARGET_PATH to be a file but it is not' >&2\n  exit 1\nfi\n\nif [ -z \"${TARGET_DIR}\" ] ; then\n  echo 'Expected $TARGET_DIR to be defined but it is not' >&2\n  exit 1\nelif ! [ -d \"${TARGET_DIR}\" ] ; then\n  echo 'Expected $TARGET_DIR to be a directory but it is not' >&2\n  exit 1\nfi\n\nif [ -z \"${TARGET_NAME}\" ] ; then\n  echo 'Expected $TARGET_NAME to be defined but it is not' >&2\n  exit 1\nfi\n\nif [ -z \"${PDB2MDB}\" ] ; then\n  echo '$PDB2MDB not found'\nelse\n  echo \"Running '${PDB2MDB}'\"\n  \"${PDB2MDB}\" \"${TARGET_PATH}\"\nfi\n\nif [ -z \"${KSPDIR}\" ] ; then\n  echo '$KSPDIR not found'\nelse\n  if ! [ -d \"${KSPDIR}\" ] ; then\n    echo 'Expected $KSPDIR to point to a directory but it is not' >&2\n    exit 1\n  fi\n  if ! [ -d \"${KSPDIR}/GameData\" ] ; then\n    echo 'Expected $KSPDIR to contain a GameData subdirectory but it does not' >&2\n    exit 1\n  fi\n  echo \"Copying to '${KSPDIR}'\"\n  cp \"${TARGET_PATH}\" \"${KSPDIR}/GameData/\"\n  test -f \"${TARGET_DIR}/${TARGET_NAME}.pdb\" && cp \"${TARGET_DIR}/${TARGET_NAME}.pdb\" \"${KSPDIR}/GameData/\"\n  test -f \"${TARGET_DIR}/${TARGET_NAME}.dll.mdb\" && cp \"${TARGET_DIR}/${TARGET_NAME}.dll.mdb\" \"${KSPDIR}/GameData/\"\n  rm -f \"${KSPDIR}/GameData/ModuleManager.ConfigCache\"\nfi\n"
  },
  {
    "path": "ModuleManager/packages.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"CopyLocalFalse\" version=\"1.0.2\" targetFramework=\"net35\" />\n</packages>"
  },
  {
    "path": "ModuleManager.sln",
    "content": "﻿\r\nMicrosoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio 15\r\nVisualStudioVersion = 15.0.26730.12\r\nMinimumVisualStudioVersion = 10.0.40219.1\r\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"ModuleManager\", \"ModuleManager\\ModuleManager.csproj\", \"{02C8E3AF-69F9-4102-AB60-DD6DE60662D3}\"\r\nEndProject\r\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"ModuleManagerTests\", \"ModuleManagerTests\\ModuleManagerTests.csproj\", \"{BC2A08C8-64EF-4823-A40B-8889C1CCFD75}\"\r\nEndProject\r\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"TestUtils\", \"TestUtils\\TestUtils.csproj\", \"{20EAAFE6-510D-4374-8D2F-6B52D0178E85}\"\r\nEndProject\r\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"TestUtilsTests\", \"TestUtilsTests\\TestUtilsTests.csproj\", \"{E695C11F-4217-4014-9B51-7232A654C205}\"\r\nEndProject\r\nGlobal\r\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n\t\tDebug|Any CPU = Debug|Any CPU\r\n\t\tRelease|Any CPU = Release|Any CPU\r\n\tEndGlobalSection\r\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n\t\t{02C8E3AF-69F9-4102-AB60-DD6DE60662D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r\n\t\t{02C8E3AF-69F9-4102-AB60-DD6DE60662D3}.Debug|Any CPU.Build.0 = Debug|Any CPU\r\n\t\t{02C8E3AF-69F9-4102-AB60-DD6DE60662D3}.Release|Any CPU.ActiveCfg = Release|Any CPU\r\n\t\t{02C8E3AF-69F9-4102-AB60-DD6DE60662D3}.Release|Any CPU.Build.0 = Release|Any CPU\r\n\t\t{BC2A08C8-64EF-4823-A40B-8889C1CCFD75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r\n\t\t{BC2A08C8-64EF-4823-A40B-8889C1CCFD75}.Debug|Any CPU.Build.0 = Debug|Any CPU\r\n\t\t{BC2A08C8-64EF-4823-A40B-8889C1CCFD75}.Release|Any CPU.ActiveCfg = Release|Any CPU\r\n\t\t{BC2A08C8-64EF-4823-A40B-8889C1CCFD75}.Release|Any CPU.Build.0 = Release|Any CPU\r\n\t\t{20EAAFE6-510D-4374-8D2F-6B52D0178E85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r\n\t\t{20EAAFE6-510D-4374-8D2F-6B52D0178E85}.Debug|Any CPU.Build.0 = Debug|Any CPU\r\n\t\t{20EAAFE6-510D-4374-8D2F-6B52D0178E85}.Release|Any CPU.ActiveCfg = Release|Any CPU\r\n\t\t{20EAAFE6-510D-4374-8D2F-6B52D0178E85}.Release|Any CPU.Build.0 = Release|Any CPU\r\n\t\t{E695C11F-4217-4014-9B51-7232A654C205}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r\n\t\t{E695C11F-4217-4014-9B51-7232A654C205}.Debug|Any CPU.Build.0 = Debug|Any CPU\r\n\t\t{E695C11F-4217-4014-9B51-7232A654C205}.Release|Any CPU.ActiveCfg = Release|Any CPU\r\n\t\t{E695C11F-4217-4014-9B51-7232A654C205}.Release|Any CPU.Build.0 = Release|Any CPU\r\n\tEndGlobalSection\r\n\tGlobalSection(SolutionProperties) = preSolution\r\n\t\tHideSolutionNode = FALSE\r\n\tEndGlobalSection\r\n\tGlobalSection(ExtensibilityGlobals) = postSolution\r\n\t\tSolutionGuid = {4DCC10BA-6036-4DCF-A78B-086DF4487922}\r\n\tEndGlobalSection\r\n\tGlobalSection(MonoDevelopProperties) = preSolution\r\n\t\tStartupItem = ModuleManager.csproj\r\n\t\toutputpath = ..\\..\\Plugins\r\n\tEndGlobalSection\r\nEndGlobal\r\n"
  },
  {
    "path": "ModuleManager.sln.DotSettings",
    "content": "﻿<wpf:ResourceDictionary xml:space=\"preserve\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:s=\"clr-namespace:System;assembly=mscorlib\" xmlns:ss=\"urn:shemas-jetbrains-com:settings-storage-xaml\" xmlns:wpf=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">\n\t<s:Boolean x:Key=\"/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=ProceduralParts_002EAnnotations/@EntryIndexedValue\">True</s:Boolean>\n\t<s:String x:Key=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=ClassNeverInstantiated_002EGlobal/@EntryIndexedValue\">DO_NOT_SHOW</s:String>\n\t<s:String x:Key=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToConstant_002EGlobal/@EntryIndexedValue\">DO_NOT_SHOW</s:String>\n\t<s:String x:Key=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=EventNeverSubscribedTo_002EGlobal/@EntryIndexedValue\">DO_NOT_SHOW</s:String>\n\t<s:String x:Key=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=FieldCanBeMadeReadOnly_002EGlobal/@EntryIndexedValue\">DO_NOT_SHOW</s:String>\n\t<s:String x:Key=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBePrivate_002EGlobal/@EntryIndexedValue\">DO_NOT_SHOW</s:String>\n\t<s:String x:Key=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=UnassignedField_002EGlobal/@EntryIndexedValue\">DO_NOT_SHOW</s:String>\n\t<s:String x:Key=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMember_002EGlobal/@EntryIndexedValue\">DO_NOT_SHOW</s:String>\n\t<s:String x:Key=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedParameter_002EGlobal/@EntryIndexedValue\">DO_NOT_SHOW</s:String>\n\t<s:String x:Key=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=NotAccessedField_002EGlobal/@EntryIndexedValue\">DO_NOT_SHOW</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FAR/@EntryIndexedValue\">FAR</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ISP/@EntryIndexedValue\">ISP</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=KSP/@EntryIndexedValue\">KSP</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ME/@EntryIndexedValue\">ME</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SFS/@EntryIndexedValue\">SFS</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue\">UI</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue\">&lt;Policy Inspect=\"True\" Prefix=\"_\" Suffix=\"\" Style=\"aaBb\"&gt;&lt;ExtraRule Prefix=\"\" Suffix=\"\" Style=\"aaBb\" /&gt;&lt;/Policy&gt;</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue\">&lt;Policy Inspect=\"True\" Prefix=\"_\" Suffix=\"\" Style=\"aaBb\"&gt;&lt;ExtraRule Prefix=\"\" Suffix=\"\" Style=\"aaBb\" /&gt;&lt;/Policy&gt;</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue\">&lt;Policy Inspect=\"True\" Prefix=\"\" Suffix=\"\" Style=\"AaBb\"&gt;&lt;ExtraRule Prefix=\"\" Suffix=\"\" Style=\"aaBb\" /&gt;&lt;/Policy&gt;</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PublicFields/@EntryIndexedValue\">&lt;Policy Inspect=\"True\" Prefix=\"\" Suffix=\"\" Style=\"AaBb\"&gt;&lt;ExtraRule Prefix=\"\" Suffix=\"\" Style=\"aaBb\" /&gt;&lt;/Policy&gt;</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GUI/@EntryIndexedValue\">GUI</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue\">ID</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UV/@EntryIndexedValue\">UV</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XZU/@EntryIndexedValue\">XZU</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=KL/@EntryIndexedValue\">KL</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SRB/@EntryIndexedValue\">SRB</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TS/@EntryIndexedValue\">TS</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SPH/@EntryIndexedValue\">SPH</s:String>\n\t<s:String x:Key=\"/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=VAB/@EntryIndexedValue\">VAB</s:String>\n</wpf:ResourceDictionary>"
  },
  {
    "path": "ModuleManagerTests/Collections/ArrayEnumeratorTest.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing Xunit;\nusing ModuleManager.Collections;\n\nnamespace ModuleManagerTests.Collections\n{\n    public class ArrayEnumeratorTest\n    {\n        [Fact]\n        public void Test__Constructor__ArrayNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new ArrayEnumerator<string>(null);\n            });\n\n            Assert.Equal(\"array\", ex.ParamName);\n        }\n\n        [Fact]\n        public void Test__Constructor__StartIndex__Negative()\n        {\n            string[] arr = { \"abc\", \"def\", \"ghi\" };\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new ArrayEnumerator<string>(arr, -1);\n            });\n\n            Assert.Equal(\"startIndex\", ex.ParamName);\n            Assert.Contains(\"must be non-negative (got -1)\", ex.Message);\n        }\n\n        [Fact]\n        public void Test__Constructor__StartIndex__TooLarge()\n        {\n            string[] arr = { \"abc\", \"def\", \"ghi\" };\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new ArrayEnumerator<string>(arr, 4);\n            });\n\n            Assert.Equal(\"startIndex\", ex.ParamName);\n            Assert.Contains(\"must be less than or equal to array length (array length 3, startIndex 4)\", ex.Message);\n        }\n\n        [Fact]\n        public void Test__Constructor__Length__Negative()\n        {\n            string[] arr = { \"abc\", \"def\", \"ghi\" };\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new ArrayEnumerator<string>(arr, 1, -1);\n            });\n\n            Assert.Equal(\"length\", ex.ParamName);\n            Assert.Contains(\"must be non-negative (got -1)\", ex.Message);\n        }\n\n        [Fact]\n        public void Test__Constructor__Length__TooLong()\n        {\n            string[] arr = { \"abc\", \"def\", \"ghi\" };\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new ArrayEnumerator<string>(arr, 1, 3);\n            });\n\n            Assert.Equal(\"length\", ex.ParamName);\n            Assert.Contains(\"must fit within the string (array length 3, startIndex 1, length 3)\", ex.Message);\n        }\n\n        [Fact]\n        public void TestArrayEnumerator()\n        {\n            string[] arr = { \"abc\", \"def\", \"ghi\" };\n\n            IEnumerator<string> enumerator = new ArrayEnumerator<string>(arr);\n\n            Assert.True(enumerator.MoveNext());\n            Assert.Equal(\"abc\", enumerator.Current);\n            Assert.True(enumerator.MoveNext());\n            Assert.Equal(\"def\", enumerator.Current);\n            Assert.True(enumerator.MoveNext());\n            Assert.Equal(\"ghi\", enumerator.Current);\n            Assert.False(enumerator.MoveNext());\n        }\n\n        [Fact]\n        public void TestArrayEnumerator__Reset()\n        {\n            string[] arr = { \"abc\", \"def\" };\n\n            IEnumerator<string> enumerator = new ArrayEnumerator<string>(arr);\n\n            Assert.True(enumerator.MoveNext());\n            Assert.Equal(\"abc\", enumerator.Current);\n            Assert.True(enumerator.MoveNext());\n            Assert.Equal(\"def\", enumerator.Current);\n            Assert.False(enumerator.MoveNext());\n\n            enumerator.Reset();\n\n            Assert.True(enumerator.MoveNext());\n            Assert.Equal(\"abc\", enumerator.Current);\n            Assert.True(enumerator.MoveNext());\n            Assert.Equal(\"def\", enumerator.Current);\n            Assert.False(enumerator.MoveNext());\n        }\n\n        [Fact]\n        public void TestArrayEnumerator__Empty()\n        {\n            string[] arr = new string[0];\n            IEnumerator<string> enumerator = new ArrayEnumerator<string>(arr);\n            Assert.False(enumerator.MoveNext());\n        }\n\n        [Fact]\n        public void TestArrayEnumerator__StartIndex()\n        {\n            string[] arr = { \"abc\", \"def\", \"ghi\" };\n\n            IEnumerator<string> enumerator = new ArrayEnumerator<string>(arr, 1);\n\n            Assert.True(enumerator.MoveNext());\n            Assert.Equal(\"def\", enumerator.Current);\n            Assert.True(enumerator.MoveNext());\n            Assert.Equal(\"ghi\", enumerator.Current);\n            Assert.False(enumerator.MoveNext());\n        }\n\n        [Fact]\n        public void TestArrayEnumerator__StartIndex__Length()\n        {\n            string[] arr = { \"abc\", \"def\", \"ghi\", \"jkl\" };\n\n            IEnumerator<string> enumerator = new ArrayEnumerator<string>(arr, 1, 2);\n\n            Assert.True(enumerator.MoveNext());\n            Assert.Equal(\"def\", enumerator.Current);\n            Assert.True(enumerator.MoveNext());\n            Assert.Equal(\"ghi\", enumerator.Current);\n            Assert.False(enumerator.MoveNext());\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Collections/ImmutableStackTest.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing Xunit;\n\nusing ModuleManager.Collections;\n\nnamespace ModuleManagerTests.Collections\n{\n    public class ImmutableStackTest\n    {\n        [Fact]\n        public void TestValue()\n        {\n            object obj = new object();\n            ImmutableStack<object> stack = new ImmutableStack<object>(obj);\n\n            Assert.Same(obj, stack.value);\n        }\n\n        [Fact]\n        public void TestIsRoot()\n        {\n            ImmutableStack<object> stack1 = new ImmutableStack<object>(new object());\n            ImmutableStack<object> stack2 = stack1.Push(new object());\n\n            Assert.True(stack1.IsRoot);\n            Assert.False(stack2.IsRoot);\n        }\n\n        [Fact]\n        public void TestRoot()\n        {\n            ImmutableStack<object> stack1 = new ImmutableStack<object>(new object());\n            ImmutableStack<object> stack2 = stack1.Push(new object());\n            ImmutableStack<object> stack3 = stack2.Push(new object());\n\n            Assert.Same(stack1, stack1.Root);\n            Assert.Same(stack1, stack2.Root);\n            Assert.Same(stack1, stack3.Root);\n        }\n\n        [Fact]\n        public void TestDepth()\n        {\n            ImmutableStack<object> stack1 = new ImmutableStack<object>(new object());\n            ImmutableStack<object> stack2 = stack1.Push(new object());\n            ImmutableStack<object> stack3 = stack2.Push(new object());\n\n            Assert.Equal(1, stack1.Depth);\n            Assert.Equal(2, stack2.Depth);\n            Assert.Equal(3, stack3.Depth);\n        }\n\n        [Fact]\n        public void TestPush()\n        {\n            object obj1 = new object();\n            object obj2 = new object();\n            object obj3 = new object();\n            ImmutableStack<object> stack1 = new ImmutableStack<object>(obj1);\n            ImmutableStack<object> stack2 = stack1.Push(obj2);\n            ImmutableStack<object> stack3 = stack2.Push(obj3);\n\n            Assert.Same(stack2, stack3.parent);\n            Assert.Same(stack1, stack2.parent);\n\n            Assert.Same(obj1, stack1.value);\n            Assert.Same(obj2, stack2.value);\n            Assert.Same(obj3, stack3.value);\n        }\n\n        [Fact]\n        public void TestPop()\n        {\n            object obj1 = new object();\n            object obj2 = new object();\n            object obj3 = new object();\n            ImmutableStack<object> stack = new ImmutableStack<object>(obj1).Push(obj2).Push(obj3);\n\n            Assert.Same(obj1, stack.Pop().Pop().value);\n            Assert.Same(obj2, stack.Pop().value);\n            Assert.Same(obj3, stack.value);\n        }\n\n        [Fact]\n        public void TestPop__Root()\n        {\n            ImmutableStack<object> stack = new ImmutableStack<object>(new object());\n\n            Assert.Throws<InvalidOperationException>(delegate\n            {\n                stack.Pop();\n            });\n        }\n\n        [Fact]\n        public void TestReplaceValue()\n        {\n            object obj1 = new object();\n            object obj2 = new object();\n            object obj3 = new object();\n            ImmutableStack<object> stack1 = new ImmutableStack<object>(obj1);\n            ImmutableStack<object> stack2 = stack1.Push(obj2);\n\n            ImmutableStack<object> stack3 = stack2.ReplaceValue(obj3);\n\n            Assert.Same(obj3, stack3.value);\n            Assert.Same(stack1, stack3.parent);\n        }\n\n        [Fact]\n        public void TestReplaceValue__Root()\n        {\n            object obj1 = new object();\n            object obj2 = new object();\n            ImmutableStack<object> stack1 = new ImmutableStack<object>(obj1);\n\n            ImmutableStack<object> stack2 = stack1.ReplaceValue(obj2);\n\n            Assert.Same(obj2, stack2.value);\n            Assert.Null(stack2.parent);\n        }\n\n        [Fact]\n        public void TestEnumerator()\n        {\n            object obj1 = new object();\n            object obj2 = new object();\n            object obj3 = new object();\n            ImmutableStack<object> stack = new ImmutableStack<object>(obj1).Push(obj2).Push(obj3);\n\n            object[] objs = stack.ToArray();\n\n            Assert.Equal(3, objs.Length);\n            Assert.Same(obj3, objs[0]);\n            Assert.Same(obj2, objs[1]);\n            Assert.Same(obj1, objs[2]);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Collections/KeyValueCacheTest.cs",
    "content": "using System;\nusing Xunit;\nusing ModuleManager.Collections;\n\nnamespace ModuleManagerTests.Collections\n{\n    public class KeyValueCacheTest\n    {\n        [Fact]\n        public void TestFetch__CreateValueNull()\n        {\n            KeyValueCache<object, object> cache = new KeyValueCache<object, object>();\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                cache.Fetch(new object(), null);\n            });\n\n            Assert.Equal(\"createValue\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestFetch__KeyNotPresent()\n        {\n            object key = new object();\n            object value = new object();\n            KeyValueCache<object, object> cache = new KeyValueCache<object, object>();\n\n            object fetchedValue = cache.Fetch(key, () => value);\n\n            Assert.Same(value, fetchedValue);\n        }\n\n        [Fact]\n        public void TestFetch__KeyPresent()\n        {\n            object key = new object();\n            object value = new object();\n            KeyValueCache<object, object> cache = new KeyValueCache<object, object>();\n\n            cache.Fetch(key, () => value);\n\n            bool called2ndTime = false;\n            object fetchedValue = cache.Fetch(key, delegate\n            {\n                called2ndTime = true;\n                return null;\n            });\n\n            Assert.Same(value, fetchedValue);\n            Assert.False(called2ndTime);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Collections/MessageQueueTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing ModuleManager.Collections;\n\nnamespace ModuleManagerTests.Collections\n{\n    public class MessageQueueTest\n    {\n        private class TestClass { }\n\n        private readonly MessageQueue<TestClass> queue = new MessageQueue<TestClass>();\n\n        [Fact]\n        public void Test__Empty()\n        {\n            Assert.Empty(queue);\n        }\n\n        [Fact]\n        public void TestAdd()\n        {\n            TestClass o1 = new TestClass();\n            TestClass o2 = new TestClass();\n            TestClass o3 = new TestClass();\n\n            queue.Add(o1);\n            queue.Add(o2);\n            queue.Add(o3);\n\n            Assert.Equal(new[] { o1, o2, o3 }, queue);\n        }\n\n        [Fact]\n        public void TestTakeAll()\n        {\n            TestClass o1 = new TestClass();\n            TestClass o2 = new TestClass();\n            TestClass o3 = new TestClass();\n            TestClass o4 = new TestClass();\n\n            queue.Add(o1);\n            queue.Add(o2);\n            queue.Add(o3);\n\n            MessageQueue<TestClass> queue2 = Assert.IsType<MessageQueue<TestClass>>(queue.TakeAll());\n\n            queue.Add(o4);\n\n            Assert.Equal(new[] { o4 }, queue);\n            Assert.Equal(new[] { o1, o2, o3 }, queue2);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/CommandParserTest.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Xunit;\nusing ModuleManager;\n\nnamespace ModuleManagerTests\n{\n    public class CommandParserTest\n    {\n        [Fact]\n        public void TestParse__Insert()\n        {\n            Assert.Equal(Command.Insert, CommandParser.Parse(\"PART\", out string newName));\n            Assert.Equal(\"PART\", newName);\n        }\n\n        [Fact]\n        public void TestParse__Delete()\n        {\n            Assert.Equal(Command.Delete, CommandParser.Parse(\"!PART\", out string newName1));\n            Assert.Equal(\"PART\", newName1);\n            Assert.Equal(Command.Delete, CommandParser.Parse(\"-PART\", out string newName2));\n            Assert.Equal(\"PART\", newName2);\n        }\n\n        [Fact]\n        public void TestParse__Edit()\n        {\n            Assert.Equal(Command.Edit, CommandParser.Parse(\"@PART\", out string newName));\n            Assert.Equal(\"PART\", newName);\n        }\n\n        [Fact]\n        public void TestParse__Replace()\n        {\n            Assert.Equal(Command.Replace, CommandParser.Parse(\"%PART\", out string newName));\n            Assert.Equal(\"PART\", newName);\n        }\n\n        [Fact]\n        public void TestParse__Copy()\n        {\n            Assert.Equal(Command.Copy, CommandParser.Parse(\"+PART\", out string newName1));\n            Assert.Equal(\"PART\", newName1);\n            Assert.Equal(Command.Copy, CommandParser.Parse(\"$PART\", out string newName2));\n            Assert.Equal(\"PART\", newName2);\n        }\n\n        [Fact]\n        public void TestParse__Rename()\n        {\n            Assert.Equal(Command.Rename, CommandParser.Parse(\"|PART\", out string newName));\n            Assert.Equal(\"PART\", newName); ;\n        }\n\n        [Fact]\n        public void TestParse__Paste()\n        {\n            Assert.Equal(Command.Paste, CommandParser.Parse(\"#PART\", out string newName));\n            Assert.Equal(\"PART\", newName);\n        }\n\n        [Fact]\n        public void TestParse__Special()\n        {\n            Assert.Equal(Command.Special, CommandParser.Parse(\"*PART\", out string newName));\n            Assert.Equal(\"PART\", newName);\n        }\n\n        [Fact]\n        public void TestParse__Special__Chained()\n        {\n            Assert.Equal(Command.Special, CommandParser.Parse(\"*@PART\", out string newName));\n            Assert.Equal(\"@PART\", newName);\n        }\n\n        [Fact]\n        public void TestParse__Create()\n        {\n            Assert.Equal(Command.Create, CommandParser.Parse(\"&PART\", out string newName));\n            Assert.Equal(\"PART\", newName);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/DummyTest.cs",
    "content": "﻿using System;\nusing Xunit;\n\nnamespace ModuleManagerTests\n{\n    public class DummyTest\n    {\n        [Fact]\n        public void PassingTest()\n        {\n            Assert.True(true);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Extensions/ByteArrayExtensionsTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing ModuleManager.Extensions;\n\nnamespace ModuleManagerTests.Extensions\n{\n    public class ByteArrayExtensionsTest\n    {\n        [Fact]\n        public void TestToHex()\n        {\n            byte[] data = { 0x00, 0xff, 0x01, 0xfe, 0x02, 0xfd, 0x9a };\n\n            Assert.Equal(\"00ff01fe02fd9a\", data.ToHex());\n        }\n\n        [Fact]\n        public void TestToHex__NullData()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                ByteArrayExtensions.ToHex(null);\n            });\n\n            Assert.Equal(\"data\", ex.ParamName);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs",
    "content": "﻿using System;\nusing System.Text;\nusing Xunit;\nusing TestUtils;\nusing ModuleManager.Extensions;\n\nnamespace ModuleManagerTests.Extensions\n{\n    public class ConfigNodeExtensionsTest\n    {\n        [Fact]\n        public void TestShallowCopyFrom()\n        {\n            ConfigNode fromNode = new TestConfigNode(\"SOME_NODE\")\n            {\n                { \"abc\", \"def\" },\n                { \"ghi\", \"jkl\" },\n                new TestConfigNode(\"INNER_NODE_1\")\n                {\n                    { \"mno\", \"pqr\" },\n                    new TestConfigNode(\"INNER_INNER_NODE_1\"),\n                },\n                new TestConfigNode(\"INNER_NODE_2\")\n                {\n                    { \"stu\", \"vwx\" },\n                    new TestConfigNode(\"INNER_INNER_NODE_2\"),\n                },\n            };\n\n            ConfigNode.Value value1 = fromNode.values[0];\n            ConfigNode.Value value2 = fromNode.values[1];\n\n            ConfigNode innerNode1 = fromNode.nodes[0];\n            ConfigNode innerNode2 = fromNode.nodes[1];\n\n            ConfigNode toNode = new TestConfigNode(\"SOME_OTHER_NODE\")\n            {\n                { \"value\", \"will be removed\" },\n                new TestConfigNode(\"NODE_WILL_BE_REMOVED\"),\n            };\n\n            toNode.ShallowCopyFrom(fromNode);\n\n            Assert.Equal(\"SOME_NODE\", fromNode.name);\n            Assert.Equal(\"SOME_OTHER_NODE\", toNode.name);\n\n            Assert.Equal(2, fromNode.values.Count);\n            Assert.Equal(2, toNode.values.Count);\n\n            Assert.Same(value1, fromNode.values[0]);\n            Assert.Same(value1, toNode.values[0]);\n            AssertValue(\"abc\", \"def\", value1);\n\n            Assert.Same(value2, fromNode.values[1]);\n            Assert.Same(value2, toNode.values[1]);\n            AssertValue(\"ghi\", \"jkl\", value2);\n\n            Assert.Equal(2, fromNode.nodes.Count);\n            Assert.Equal(2, toNode.nodes.Count);\n\n            Assert.Same(innerNode1, fromNode.nodes[0]);\n            Assert.Same(innerNode1, toNode.nodes[0]);\n            Assert.Equal(\"INNER_NODE_1\", innerNode1.name);\n            Assert.Equal(1, innerNode1.values.Count);\n            AssertValue(\"mno\", \"pqr\", innerNode1.values[0]);\n            Assert.Equal(1, innerNode1.nodes.Count);\n            Assert.Equal(\"INNER_INNER_NODE_1\", innerNode1.nodes[0].name);\n            Assert.Empty(innerNode1.nodes[0].values);\n            Assert.Empty(innerNode1.nodes[0].nodes);\n\n            Assert.Same(innerNode2, fromNode.nodes[1]);\n            Assert.Same(innerNode2, toNode.nodes[1]);\n            Assert.Equal(\"INNER_NODE_2\", innerNode2.name);\n            Assert.Equal(1, innerNode2.values.Count);\n            AssertValue(\"stu\", \"vwx\", innerNode2.values[0]);\n            Assert.Equal(1, innerNode2.nodes.Count);\n            Assert.Equal(\"INNER_INNER_NODE_2\", innerNode2.nodes[0].name);\n            Assert.Empty(innerNode2.nodes[0].values);\n            Assert.Empty(innerNode2.nodes[0].nodes);\n        }\n\n        [Fact]\n        public void TestDeepCopy()\n        {\n            ConfigNode fromNode = new TestConfigNode(\"SOME_NODE\")\n            {\n                { \"abc\", \"def\" },\n                { \"ghi\", \"jkl\" },\n                new TestConfigNode(\"INNER_NODE_1\")\n                {\n                    { \"mno\", \"pqr\" },\n                    { \"weird_values\", \"some\\r\\n\\tstuff\" },\n                    new TestConfigNode(\"INNER_INNER_NODE_1\"),\n                },\n                new TestConfigNode(\"INNER_NODE_2\")\n                {\n                    { \"stu\", \"vwx\" },\n                    new TestConfigNode(\"INNER_INNER_NODE_2\"),\n                },\n            };\n            \n            ConfigNode toNode = fromNode.DeepCopy();\n            \n            Assert.Equal(\"SOME_NODE\", toNode.name);\n            \n            Assert.Equal(2, toNode.values.Count);\n            \n            Assert.NotSame(fromNode.values[0], toNode.values[0]);\n            AssertValue(\"abc\", \"def\", toNode.values[0]);\n\n            Assert.NotSame(fromNode.values[1], toNode.values[1]);\n            AssertValue(\"ghi\", \"jkl\", toNode.values[1]);\n\n            Assert.Equal(2, toNode.nodes.Count);\n\n            ConfigNode innerNode1 = toNode.nodes[0];\n            Assert.NotSame(fromNode.nodes[0], innerNode1);\n            Assert.Equal(\"INNER_NODE_1\", innerNode1.name);\n            Assert.Equal(2, innerNode1.values.Count);\n            Assert.NotSame(fromNode.nodes[0].values[0], innerNode1.values[0]);\n            AssertValue(\"mno\", \"pqr\", innerNode1.values[0]);\n            Assert.NotSame(fromNode.nodes[0].values[1], innerNode1.values[1]);\n            AssertValue(\"weird_values\", \"some\\r\\n\\tstuff\", innerNode1.values[1]);\n            Assert.Equal(1, toNode.nodes[0].nodes.Count);\n            Assert.NotSame(fromNode.nodes[0].nodes[0], innerNode1.nodes[0]);\n            Assert.Equal(\"INNER_INNER_NODE_1\", innerNode1.nodes[0].name);\n            Assert.Empty(innerNode1.nodes[0].values);\n            Assert.Empty(innerNode1.nodes[0].nodes);\n\n            ConfigNode innerNode2 = toNode.nodes[1];\n            Assert.NotSame(fromNode.nodes[1], innerNode2);\n            Assert.Equal(\"INNER_NODE_2\", innerNode2.name);\n            Assert.Equal(1, innerNode2.values.Count);\n            Assert.NotSame(fromNode.nodes[1].values[0], innerNode2.values[0]);\n            AssertValue(\"stu\", \"vwx\", innerNode2.values[0]);\n            Assert.Equal(1, innerNode2.nodes.Count);\n            Assert.NotSame(fromNode.nodes[1].nodes[0], innerNode2.nodes[0]);\n            Assert.Equal(\"INNER_INNER_NODE_2\", innerNode2.nodes[0].name);\n            Assert.Empty(innerNode2.nodes[0].values);\n            Assert.Empty(innerNode2.nodes[0].nodes);\n        }\n\n        [Fact]\n        public void TestPrettyPrint()\n        {\n            ConfigNode node = new TestConfigNode(\"SOME_NODE\")\n            {\n                { \"abc\", \"def\" },\n                { \"ghi\", \"jkl\" },\n                new TestConfigNode(\"INNER_NODE_1\")\n                {\n                    { \"mno\", \"pqr\" },\n                    new TestConfigNode(\"INNER_INNER_NODE_1\"),\n                },\n                new TestConfigNode(\"INNER_NODE_2\")\n                {\n                    { \"stu\", \"vwx\" },\n                    new TestConfigNode(\"INNER_INNER_NODE_2\"),\n                },\n            };\n\n            string expected = @\"\nXXSOME_NODE\nXX{\nXX  abc = def\nXX  ghi = jkl\nXX  INNER_NODE_1\nXX  {\nXX    mno = pqr\nXX    INNER_INNER_NODE_1\nXX    {\nXX    }\nXX  }\nXX  INNER_NODE_2\nXX  {\nXX    stu = vwx\nXX    INNER_INNER_NODE_2\nXX    {\nXX    }\nXX  }\nXX}\n\".TrimStart().Replace(\"\\r\", null);\n\n            StringBuilder sb = new StringBuilder();\n            node.PrettyPrint(ref sb, \"XX\");\n\n            Assert.Equal(expected, sb.ToString());\n        }\n\n        [Fact]\n        public void TestPrettyPrint__NullNode()\n        {\n            ConfigNode node = null;\n            StringBuilder sb = new StringBuilder();\n            node.PrettyPrint(ref sb, \"XX\");\n            Assert.Equal(\"XX<null node>\\n\", sb.ToString());\n        }\n\n        [Fact]\n        public void TestPrettyPrint__NullStringBuilder()\n        {\n            ConfigNode node = new ConfigNode(\"NODE\");\n            StringBuilder sb = null;\n            Assert.Throws<ArgumentNullException>(delegate\n            {\n                node.PrettyPrint(ref sb, \"XX\");\n            });\n        }\n\n        [Fact]\n        public void TestPrettyPrint__NullIndent()\n        {\n            ConfigNode node = new TestConfigNode(\"SOME_NODE\")\n            {\n                { \"abc\", \"def\" },\n                { \"ghi\", \"jkl\" },\n                new TestConfigNode(\"INNER_NODE\")\n                {\n                    { \"mno\", \"pqr\" },\n                },\n            };\n\n            string expected = @\"\nSOME_NODE\n{\n  abc = def\n  ghi = jkl\n  INNER_NODE\n  {\n    mno = pqr\n  }\n}\n\".TrimStart().Replace(\"\\r\", null);\n\n            StringBuilder sb = new StringBuilder();\n            node.PrettyPrint(ref sb, null);\n            Assert.Equal(expected, sb.ToString());\n        }\n\n        [Fact]\n        public void TestPrettyPrint__NullName()\n        {\n            ConfigNode node = new TestConfigNode()\n            {\n                { \"abc\", \"def\" },\n                { \"ghi\", \"jkl\" },\n                new TestConfigNode(\"INNER_NODE\")\n                {\n                    { \"mno\", \"pqr\" },\n                },\n            };\n\n            node.name = null;\n\n            string expected = @\"\nXX<null>\nXX{\nXX  abc = def\nXX  ghi = jkl\nXX  INNER_NODE\nXX  {\nXX    mno = pqr\nXX  }\nXX}\n\".TrimStart().Replace(\"\\r\", null);\n\n            StringBuilder sb = new StringBuilder();\n            node.PrettyPrint(ref sb, \"XX\");\n            Assert.Equal(expected, sb.ToString());\n        }\n\n        [Fact]\n        public void TestAddValueSafe()\n        {\n            ConfigNode node = new TestConfigNode\n            {\n                { \"key1\", \"value1\" },\n            };\n\n            node.AddValueSafe(\"weird_values\", \"some\\r\\n\\tstuff\");\n\n            Assert.Equal(2, node.values.Count);\n            AssertValue(\"key1\", \"value1\", node.values[0]);\n            AssertValue(\"weird_values\", \"some\\r\\n\\tstuff\", node.values[1]);\n        }\n\n        [Fact]\n        public void TestEscapeValuesRecursive()\n        {\n            ConfigNode node = new TestConfigNode\n            {\n                { \"key1\", \"value1\" },\n                { \"key2\", \"value\\nwith\\rescped\\tchars\" },\n                new TestConfigNode(\"SUBNODE\")\n                {\n                    { \"key3\", \"value\\nwith\\rescped\\tchars2\" },\n                },\n            };\n\n            node.EscapeValuesRecursive();\n\n            Assert.Equal(2, node.values.Count);\n            AssertValue(\"key1\", \"value1\", node.values[0]);\n            AssertValue(\"key2\", \"value\\\\nwith\\\\rescped\\\\tchars\", node.values[1]);\n            Assert.Equal(1, node.nodes.Count);\n            Assert.Equal(1, node.nodes[0].values.Count);\n            AssertValue(\"key3\", \"value\\\\nwith\\\\rescped\\\\tchars2\", node.nodes[0].values[0]);\n        }\n\n        [Fact]\n        public void TestUnescapeValuesRecursive()\n        {\n            ConfigNode node = new TestConfigNode\n            {\n                { \"key1\", \"value1\" },\n                { \"key2\", \"value\\\\nwith\\\\rescped\\\\tchars\" },\n                new TestConfigNode(\"SUBNODE\")\n                {\n                    { \"key3\", \"value\\\\nwith\\\\rescped\\\\tchars2\" },\n                },\n            };\n\n            node.UnescapeValuesRecursive();\n\n            Assert.Equal(2, node.values.Count);\n            AssertValue(\"key1\", \"value1\", node.values[0]);\n            AssertValue(\"key2\", \"value\\nwith\\rescped\\tchars\", node.values[1]);\n            Assert.Equal(1, node.nodes.Count);\n            Assert.Equal(1, node.nodes[0].values.Count);\n            AssertValue(\"key3\", \"value\\nwith\\rescped\\tchars2\", node.nodes[0].values[0]);\n        }\n\n        private void AssertValue(string name, string value, ConfigNode.Value nodeValue)\n        {\n            Assert.Equal(name, nodeValue.name);\n            Assert.Equal(value, nodeValue.value);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing ModuleManager.Logging;\nusing ModuleManager.Extensions;\n\nnamespace ModuleManagerTests.Extensions\n{\n    public class IBasicLoggerExtensionsTest\n    {\n        private readonly IBasicLogger logger = Substitute.For<IBasicLogger>();\n\n        [Fact]\n        public void TestInfo()\n        {\n            logger.Info(\"well hi there\");\n            logger.AssertInfo(\"well hi there\");\n        }\n\n        [Fact]\n        public void TestWarning()\n        {\n            logger.Warning(\"I'm warning you\");\n            logger.AssertWarning(\"I'm warning you\");\n        }\n\n        [Fact]\n        public void TestError()\n        {\n            logger.Error(\"You have made a grave mistake\");\n            logger.AssertError(\"You have made a grave mistake\");\n        }\n\n        [Fact]\n        public void TestException()\n        {\n            Exception ex = new Exception();\n            logger.Exception(ex);\n            logger.AssertException(ex);\n        }\n\n        [Fact]\n        public void TestException__Null()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                logger.Exception(null);\n            });\n\n            Assert.Equal(\"exception\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestException__Message()\n        {\n            Exception ex = new Exception();\n            logger.Exception(\"a message\", ex);\n            logger.AssertException(\"a message\", ex);\n        }\n\n        [Fact]\n        public void TestException__Message__MessageNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                logger.Exception(null, new Exception());\n            });\n\n            Assert.Equal(\"message\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestException__Message__ExceptionNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                logger.Exception(\"a message\", null);\n            });\n\n            Assert.Equal(\"exception\", ex.ParamName);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Extensions/NodeStackExtensionsTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing ModuleManager.Collections;\nusing ModuleManager.Extensions;\n\nnamespace ModuleManagerTests.Extensions\n{\n    public class NodeStackExtensionsTest\n    {\n        [Fact]\n        public void TestGetPath()\n        {\n            ConfigNode node1 = new ConfigNode(\"NODE1\");\n            ConfigNode node2 = new ConfigNode(\"NODE2\");\n            ConfigNode node3 = new ConfigNode(\"NODE3\");\n\n            ImmutableStack<ConfigNode> stack = new ImmutableStack<ConfigNode>(node1).Push(node2).Push(node3);\n\n            Assert.Equal(\"NODE1/NODE2/NODE3\", stack.GetPath());\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Extensions/StringExtensionsTest.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Xunit;\nusing ModuleManager.Extensions;\n\nnamespace ModuleManagerTests.Extensions\n{\n    public class StringExtensionsTest\n    {\n        [Fact]\n        public void TestIsBracketBalanced()\n        {\n            Assert.True(\"abc[def[ghi[jkl]mno[pqr]]stu]vwx\".IsBracketBalanced());\n        }\n\n        [Fact]\n        public void TestIsBracketBalanced__NoBrackets()\n        {\n            Assert.True(\"she sells seashells by the seashore\".IsBracketBalanced());\n        }\n\n        [Fact]\n        public void TestIsBracketBalanced__Unbalanced()\n        {\n            Assert.False(\"abc[def[ghi[jkl]mno[pqr]]stuvwx\".IsBracketBalanced());\n            Assert.False(\"abcdef[ghi[jkl]mno[pqr]]stu]vwx\".IsBracketBalanced());\n        }\n\n        [Fact]\n        public void TestIsBracketBalanced__BalancedButNegative()\n        {\n            Assert.False(\"abc]def[ghi\".IsBracketBalanced());\n        }\n\n        [Fact]\n        public void TestRemoveWS()\n        {\n            Assert.Equal(\"abcdef\", \" abc \\tdef\\r\\n\\t \".RemoveWS());\n        }\n\n\n        [InlineData(\"abc\", \"b\", true, 1)]\n        [InlineData(\"abc\", \"x\", false, -1)]\n        [Theory]\n        public void TestContains(string str, string test, bool expectedResult, int expectedIndex)\n        {\n            bool result = str.Contains(test, out int index);\n            Assert.Equal(expectedResult, result);\n            Assert.Equal(expectedIndex, index);\n        }\n\n        [Fact]\n        public void TestContains__NullStr()\n        {\n            string s = null;\n            Assert.Throws<ArgumentNullException>(delegate\n            {\n                s.Contains(\"x\", out int _x);\n            });\n        }\n\n        [Fact]\n        public void TestContains__NullValue()\n        {\n            Assert.Throws<ArgumentNullException>(delegate\n            {\n                \"abc\".Contains(null, out int _x);\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing TestUtils;\nusing ModuleManager.Extensions;\n\nnamespace ModuleManagerTests.Extensions\n{\n    public class UrlConfigExtensionsTest\n    {\n        [Fact]\n        public void TestSafeUrl()\n        {\n            ConfigNode node = new TestConfigNode(\"SOME_NODE\")\n            {\n                { \"name\", \"this shouldn't show up\" },\n            };\n            UrlDir.UrlConfig url = UrlBuilder.CreateConfig(\"abc/def\", node);\n            Assert.Equal(\"abc/def/SOME_NODE\", url.SafeUrl());\n        }\n\n        [Fact]\n        public void TestSafeUrl__Null()\n        {\n            UrlDir.UrlConfig url = null;\n            Assert.Equal(\"<null>\", url.SafeUrl());\n        }\n\n        [Fact]\n        public void TestSafeUrl__NullParent()\n        {\n            UrlDir.UrlConfig url = new UrlDir.UrlConfig(null, new ConfigNode(\"SOME_NODE\"));\n            Assert.Equal(\"SOME_NODE\", url.SafeUrl());\n        }\n\n        [Fact]\n        public void TestSafeUrl__NullParent__NullName()\n        {\n            ConfigNode node = new ConfigNode\n            {\n                name = null\n            };\n            UrlDir.UrlConfig url = new UrlDir.UrlConfig(null, node);\n            Assert.Equal(\"<null>\", url.SafeUrl());\n        }\n\n        [Fact]\n        public void TestSafeUrl__NullParent__BlankName()\n        {\n            UrlDir.UrlConfig url = new UrlDir.UrlConfig(null, new ConfigNode(\" \"));\n            Assert.Equal(\"<blank>\", url.SafeUrl());\n        }\n\n        [Fact]\n        public void TestSafeUrl__NullName()\n        {\n            ConfigNode node = new TestConfigNode()\n            {\n                { \"name\", \"this shouldn't show up\" },\n            };\n            node.name = null;\n            UrlDir.UrlConfig url = UrlBuilder.CreateConfig(\"abc/def\", node);\n            Assert.Equal(\"abc/def/<null>\", url.SafeUrl());\n        }\n\n        [Fact]\n        public void TestSafeUrl__BlankName()\n        {\n            ConfigNode node = new TestConfigNode(\" \")\n            {\n                { \"name\", \"this shouldn't show up\" },\n            };\n            UrlDir.UrlConfig url = UrlBuilder.CreateConfig(\"abc/def\", node);\n            Assert.Equal(\"abc/def/<blank>\", url.SafeUrl());\n        }\n\n        [Fact]\n        public void TestPrettyPrint()\n        {\n            ConfigNode node = new TestConfigNode(\"SOME_NODE\")\n            {\n                { \"abc\", \"def\" },\n                { \"ghi\", \"jkl\" },\n                new TestConfigNode(\"INNER_NODE_1\")\n                {\n                    { \"mno\", \"pqr\" },\n                },\n            };\n\n            UrlDir.UrlConfig url = UrlBuilder.CreateConfig(\"abc/def.cfg\", node);\n\n            string expected = @\"\nabc/def/SOME_NODE\n  SOME_NODE\n  {\n    abc = def\n    ghi = jkl\n    INNER_NODE_1\n    {\n      mno = pqr\n    }\n  }\n\".TrimStart().Replace(\"\\r\", null);\n            \n            Assert.Equal(expected, url.PrettyPrint());\n        }\n\n        [Fact]\n        public void TestPrettyPrint__NullName()\n        {\n            ConfigNode node = new TestConfigNode()\n            {\n                { \"abc\", \"def\" },\n                { \"ghi\", \"jkl\" },\n                new TestConfigNode(\"INNER_NODE_1\")\n                {\n                    { \"mno\", \"pqr\" },\n                },\n            };\n\n            node.name = null;\n\n            UrlDir.UrlConfig url = UrlBuilder.CreateConfig(\"abc/def.cfg\", node);\n\n            string expected = @\"\nabc/def/<null>\n  <null>\n  {\n    abc = def\n    ghi = jkl\n    INNER_NODE_1\n    {\n      mno = pqr\n    }\n  }\n\".TrimStart().Replace(\"\\r\", null);\n\n            Assert.Equal(expected, url.PrettyPrint());\n        }\n\n        [Fact]\n        public void TestPrettyPrint__BlankName()\n        {\n            ConfigNode node = new TestConfigNode(\" \")\n            {\n                { \"abc\", \"def\" },\n                { \"ghi\", \"jkl\" },\n                new TestConfigNode(\"INNER_NODE_1\")\n                {\n                    { \"mno\", \"pqr\" },\n                },\n            };\n\n            UrlDir.UrlConfig url = UrlBuilder.CreateConfig(\"abc/def.cfg\", node);\n\n            string expected = @\"\nabc/def/<blank>\n   \n  {\n    abc = def\n    ghi = jkl\n    INNER_NODE_1\n    {\n      mno = pqr\n    }\n  }\n\".TrimStart().Replace(\"\\r\", null);\n\n            Assert.Equal(expected, url.PrettyPrint());\n        }\n\n        [Fact]\n        public void TestPrettyPrint__NullNameValue()\n        {\n            ConfigNode node = new TestConfigNode(\"SOME_NODE\")\n            {\n                { \"name\", \"temp\" },\n                { \"abc\", \"def\" },\n                { \"ghi\", \"jkl\" },\n                new TestConfigNode(\"INNER_NODE_1\")\n                {\n                    { \"mno\", \"pqr\" },\n                },\n            };\n\n            node.values[0].value = null;\n\n            UrlDir.UrlConfig url = UrlBuilder.CreateConfig(\"abc/def.cfg\", node);\n\n            string expected = @\"\nabc/def/SOME_NODE\n  SOME_NODE\n  {\n    name = <null>\n    abc = def\n    ghi = jkl\n    INNER_NODE_1\n    {\n      mno = pqr\n    }\n  }\n\".TrimStart().Replace(\"\\r\", null);\n\n            Assert.Equal(expected, url.PrettyPrint());\n        }\n\n        [Fact]\n        public void TestPrettyPrint__NullNode()\n        {\n            UrlDir.UrlConfig url = UrlBuilder.CreateConfig(\"abc/def.cfg\", new ConfigNode(\"SOME_NODE\"));\n            url.config = null;\n\n            string expected = @\"\nabc/def/SOME_NODE\n  <null node>\n\".TrimStart().Replace(\"\\r\", null);\n\n            Assert.Equal(expected, url.PrettyPrint());\n        }\n\n        [Fact]\n        public void TestPrettyPrint__Null()\n        {\n            UrlDir.UrlConfig url = null;\n            Assert.Equal(\"<null UrlConfig>\", url.PrettyPrint());\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing TestUtils;\nusing ModuleManager.Extensions;\n\nnamespace ModuleManagerTests.Extensions\n{\n    public class UrlDirExtensionsTest\n    {\n\n        [Fact]\n        public void TestFind__IndirectChild()\n        {\n            UrlDir urlDir = UrlBuilder.CreateDir(\"abc\");\n            UrlDir.UrlFile urlFile = UrlBuilder.CreateFile(\"def/ghi.cfg\", urlDir);\n\n            Assert.Equal(urlFile, urlDir.Find(\"def/ghi\"));\n        }\n\n        [Fact]\n        public void TestFind__DirectChild()\n        {\n            UrlDir urlDir = UrlBuilder.CreateDir(\"abc\");\n            UrlDir.UrlFile urlFile = UrlBuilder.CreateFile(\"def.cfg\", urlDir);\n\n            Assert.Equal(urlFile, urlDir.Find(\"def\"));\n        }\n\n        [Fact]\n        public void TestFind__Extension()\n        {\n            UrlDir urlDir = UrlBuilder.CreateDir(\"abc\");\n            UrlBuilder.CreateFile(\"def/ghi.yyy\", urlDir);\n            UrlDir.UrlFile urlFile = UrlBuilder.CreateFile(\"def/ghi.cfg\", urlDir);\n            UrlBuilder.CreateFile(\"def/ghi.zzz\", urlDir);\n\n            Assert.Equal(urlFile, urlDir.Find(\"def/ghi.cfg\"));\n        }\n\n        [Fact]\n        public void TestFind__NotFound()\n        {\n            UrlDir urlDir = UrlBuilder.CreateDir(\"abc\");\n            UrlBuilder.CreateDir(\"def\", urlDir);\n\n            Assert.Null(urlDir.Find(\"def/ghi\"));\n        }\n\n        [Fact]\n        public void TestFind__Extension__NotFound()\n        {\n            UrlDir urlDir = UrlBuilder.CreateDir(\"abc\");\n            UrlBuilder.CreateFile(\"def/ghi.yyy\", urlDir);\n            UrlBuilder.CreateFile(\"def/ghi.zzz\", urlDir);\n\n            Assert.Null(urlDir.Find(\"def/ghi.cfg\"));\n        }\n\n        [Fact]\n        public void TestFind__IntermediateDirectoryNotFound()\n        {\n            UrlDir urlDir = UrlBuilder.CreateDir(\"abc\");\n            Assert.Null(urlDir.Find(\"def/ghi\"));\n        }\n\n        [Fact]\n        public void TestFind__UrlDirNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                UrlDirExtensions.Find(null, \"abc\");\n            });\n\n            Assert.Equal(\"urlDir\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestFind__UrlNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                UrlDirExtensions.Find(UrlBuilder.CreateDir(\"abc\"), null);\n            });\n\n            Assert.Equal(\"url\", ex.ParamName);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Extensions/UrlFileExtensionsTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing TestUtils;\nusing ModuleManager.Extensions;\n\nnamespace ModuleManagerTests.Extensions\n{\n    public static class UrlFileExtensionsTest\n    {\n        [Fact]\n        public static void TestGetUrlWithExtension()\n        {\n            UrlDir.UrlFile urlFile = UrlBuilder.CreateFile(\"abc/def/ghi.cfg\");\n            Assert.Equal(\"abc/def/ghi.cfg\", urlFile.GetUrlWithExtension());\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/InGameTestRunnerTest.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing Xunit;\nusing NSubstitute;\nusing UnityEngine;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Logging;\n\nnamespace ModuleManagerTests\n{\n    public class InGameTestRunnerTest\n    {\n        private readonly IBasicLogger logger;\n        private readonly UrlDir databaseRoot;\n        private readonly InGameTestRunner testRunner;\n\n        public InGameTestRunnerTest()\n        {\n            logger = Substitute.For<IBasicLogger>();\n            databaseRoot = UrlBuilder.CreateRoot();\n            testRunner = new InGameTestRunner(logger);\n        }\n\n        [Fact]\n        public void TestConstructor__LoggerNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new InGameTestRunner(null);\n            });\n\n            Assert.Equal(\"logger\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestRunTestCases__DatabaseRootNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                testRunner.RunTestCases(null);\n            });\n\n            Assert.Equal(\"gameDatabaseRoot\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestRunTestCases__WrongNumberOfNodes()\n        {\n            UrlDir.UrlFile file1 = UrlBuilder.CreateFile(\"abc/blah1.cfg\", databaseRoot);\n\n            // Call CreateCopy otherwise XUnit sees that it's an IEnumerable and attempts to compare by enumeration\n            ConfigNode testNode1 = new TestConfigNode(\"NODE1\")\n            {\n                { \"key1\", \"value1\" },\n            }.CreateCopy();\n\n            ConfigNode testNode2 = new ConfigNode(\"NODE2\");\n\n            ConfigNode expectNode = new TestConfigNode(\"MMTEST_EXPECT\")\n            {\n                new TestConfigNode(\"NODE1\")\n                {\n                    { \"key1\", \"value1\" },\n                },\n            }.CreateCopy();\n\n            UrlBuilder.CreateConfig(testNode1, file1);\n            UrlBuilder.CreateConfig(testNode2, file1);\n            UrlBuilder.CreateConfig(expectNode, file1);\n\n            testRunner.RunTestCases(databaseRoot);\n\n            Received.InOrder(delegate\n            {\n                logger.AssertInfo(\"Running tests...\");\n                logger.AssertError($\"Test blah1 failed as expected number of nodes differs expected: 1 found: 2\");\n                logger.AssertInfo(testNode1.ToString());\n                logger.AssertInfo(testNode2.ToString());\n                logger.AssertInfo(expectNode.ToString());\n                logger.AssertInfo(\"tests complete.\");\n            });\n\n            Assert.Equal(3, file1.configs.Count);\n            Assert.Equal(testNode1, file1.configs[0].config);\n            Assert.Equal(testNode2, file1.configs[1].config);\n            Assert.Equal(expectNode, file1.configs[2].config);\n        }\n\n        [Fact]\n        public void TestRunTestCases__AllPassing()\n        {\n            UrlDir.UrlFile file1 = UrlBuilder.CreateFile(\"abc/blah1.cfg\", databaseRoot);\n            UrlDir.UrlFile file2 = UrlBuilder.CreateFile(\"abc/blah2.cfg\", databaseRoot);\n\n            ConfigNode testNode1 = new TestConfigNode(\"NODE1\")\n            {\n                { \"key1\", \"value1\" },\n                { \"key2\", \"value2\" },\n                new TestConfigNode(\"NODE2\")\n                {\n                    { \"key3\", \"value3\" },\n                },\n            };\n\n            ConfigNode testNode2 = new TestConfigNode(\"NODE3\")\n            {\n                { \"key4\", \"value4\" },\n            };\n\n            ConfigNode testNode3 = new TestConfigNode(\"NODE4\")\n            {\n                { \"key5\", \"value5\" },\n            };\n\n            UrlBuilder.CreateConfig(testNode1, file1);\n            UrlBuilder.CreateConfig(testNode2, file1);\n            UrlBuilder.CreateConfig(new TestConfigNode(\"MMTEST_EXPECT\")\n            {\n                testNode1.CreateCopy(),\n                testNode2.CreateCopy(),\n            }, file1);\n\n            UrlBuilder.CreateConfig(testNode3, file2);\n            UrlBuilder.CreateConfig(new TestConfigNode(\"MMTEST_EXPECT\")\n            {\n                testNode3.CreateCopy(),\n            }, file2);\n\n            testRunner.RunTestCases(databaseRoot);\n\n            Received.InOrder(delegate\n            {\n                logger.AssertInfo(\"Running tests...\");\n                logger.AssertInfo(\"tests complete.\");\n            });\n\n            logger.AssertNoError();\n\n            Assert.Empty(file1.configs);\n            Assert.Empty(file2.configs);\n        }\n\n        [Fact]\n        public void TestRunTestCases__Failure()\n        {\n            UrlDir.UrlFile file1 = UrlBuilder.CreateFile(\"abc/blah1.cfg\", databaseRoot);\n\n            ConfigNode testNode1 = new TestConfigNode(\"NODE1\")\n            {\n                { \"key1\", \"value1\" },\n                { \"key2\", \"value2\" },\n                new TestConfigNode(\"NODE2\")\n                {\n                    { \"key3\", \"value3\" },\n                },\n            };\n\n            ConfigNode expectNode1 = new TestConfigNode(\"NODE1\")\n            {\n                { \"key1\", \"value1\" },\n                { \"key2\", \"value2\" },\n                new TestConfigNode(\"NODE2\")\n                {\n                    { \"key4\", \"value3\" },\n                },\n            };\n\n            UrlBuilder.CreateConfig(testNode1, file1);\n            UrlBuilder.CreateConfig(new TestConfigNode(\"MMTEST_EXPECT\")\n            {\n                expectNode1,\n            }, file1);\n\n            testRunner.RunTestCases(databaseRoot);\n\n            Received.InOrder(delegate\n            {\n                logger.AssertInfo(\"Running tests...\");\n                logger.AssertError($\"Test blah1[0] failed as expected output and actual output differ.\\nexpected:\\n{expectNode1}\\nActually got:\\n{testNode1}\");\n                logger.AssertInfo(\"tests complete.\");\n            });\n\n\n            Assert.Empty(file1.configs);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Logging/LogMessageTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing UnityEngine;\nusing ModuleManager.Logging;\n\nnamespace ModuleManagerTests.Logging\n{\n    public class LogMessageTest\n    {\n        [Fact]\n        public void TestConstructor()\n        {\n            LogMessage logMessage = new LogMessage(LogType.Log, \"a message\");\n            Assert.Equal(LogType.Log, logMessage.LogType);\n            Assert.True(logMessage.Timestamp <= DateTime.Now);\n            Assert.True(logMessage.Timestamp > DateTime.Now - new TimeSpan(0, 0, 5));\n            Assert.Equal(\"a message\", logMessage.Message);\n        }\n\n        [Fact]\n        public void TestConstructor__NullMessage()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new LogMessage(LogType.Log, null);\n            });\n\n            Assert.Equal(\"message\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__FromOtherMessage()\n        {\n            ILogMessage logMessage = Substitute.For<ILogMessage>();\n            logMessage.LogType.Returns(LogType.Log);\n            logMessage.Message.Returns(\"the old message\");\n            logMessage.Timestamp.Returns(new DateTime(2000, 1, 1, 12, 34, 45, 678));\n            LogMessage newLogMessage = new LogMessage(logMessage, \"a new message\");\n            Assert.Equal(LogType.Log, newLogMessage.LogType);\n            Assert.Equal(logMessage.Timestamp, newLogMessage.Timestamp);\n            Assert.Equal(\"a new message\", newLogMessage.Message);\n        }\n\n        [Fact]\n        public void TestConstructor__FromOtherMessage__LogMessageNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new LogMessage(null, \"a new message\");\n            });\n\n            Assert.Equal(\"logMessage\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__FromOtherMessage__NewMessageNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new LogMessage(Substitute.For<ILogMessage>(), null);\n            });\n\n            Assert.Equal(\"newMessage\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestToLogMessage__Info()\n        {\n            LogMessage message = new LogMessage(LogType.Log, \"everything is ok\");\n            Assert.Matches(@\"^\\[LOG \\d\\d:\\d\\d:\\d\\d.\\d\\d\\d\\] everything is ok$\", message.ToLogString());\n        }\n\n        [Fact]\n        public void TestToLogMessage__Warning()\n        {\n            LogMessage message = new LogMessage(LogType.Warning, \"I'm warning you\");\n            Assert.Matches(@\"^\\[WRN \\d\\d:\\d\\d:\\d\\d.\\d\\d\\d\\] I'm warning you$\", message.ToLogString());\n        }\n\n        [Fact]\n        public void TestToLogMessage__Error()\n        {\n            LogMessage message = new LogMessage(LogType.Error, \"You went too far\");\n            Assert.Matches(@\"^\\[ERR \\d\\d:\\d\\d:\\d\\d.\\d\\d\\d\\] You went too far$\", message.ToLogString());\n        }\n\n        [Fact]\n        public void TestToLogMessage__Exception()\n        {\n            LogMessage message = new LogMessage(LogType.Exception, \"You went too far\");\n            Assert.Matches(@\"^\\[EXC \\d\\d:\\d\\d:\\d\\d.\\d\\d\\d\\] You went too far$\", message.ToLogString());\n        }\n\n        [Fact]\n        public void TestToLogMessage__Assert()\n        {\n            LogMessage message = new LogMessage(LogType.Assert, \"You went too far\");\n            Assert.Matches(@\"^\\[AST \\d\\d:\\d\\d:\\d\\d.\\d\\d\\d\\] You went too far$\", message.ToLogString());\n        }\n\n        [Fact]\n        public void TestToLogMessage__Unknown()\n        {\n            LogMessage message = new LogMessage((LogType)9999, \"You went too far\");\n            Assert.Matches(@\"^\\[\\?\\?\\? \\d\\d:\\d\\d:\\d\\d.\\d\\d\\d\\] You went too far$\", message.ToLogString());\n        }\n\n        [Fact]\n        public void TestToString()\n        {\n            LogMessage message = new LogMessage(LogType.Log, \"everything is ok\");\n            Assert.Equal(\"[ModuleManager.Logging.LogMessage LogType=Log Message=everything is ok]\", message.ToString());\n        }\n\n        [Fact]\n        public void TestToLogMessage__Timestamp()\n        {\n            ILogMessage logMessage = Substitute.For<ILogMessage>();\n            logMessage.LogType.Returns(LogType.Log);\n            logMessage.Timestamp.Returns(new DateTime(2000, 1, 1, 12, 34, 56, 789));\n            LogMessage message = new LogMessage(logMessage, \"everything is ok\");\n            Assert.Equal(\"[LOG 12:34:56.789] everything is ok\", message.ToLogString());\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Logging/LogSplitterTest.cs",
    "content": "using System;\nusing Xunit;\nusing NSubstitute;\nusing UnityEngine;\nusing ModuleManager.Logging;\n\nnamespace ModuleManagerTests.Logging\n{\n    public class LogSplitterTest\n    {\n        [Fact]\n        public void TestConstructor__Logger1Null()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new LogSplitter(null, Substitute.For<IBasicLogger>());\n            });\n\n            Assert.Equal(\"logger1\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__Logger2Null()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new LogSplitter(Substitute.For<IBasicLogger>(), null);\n            });\n\n            Assert.Equal(\"logger2\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestLog()\n        {\n            IBasicLogger logger1 = Substitute.For<IBasicLogger>();\n            IBasicLogger logger2 = Substitute.For<IBasicLogger>();\n            LogSplitter logSplitter = new LogSplitter(logger1, logger2);\n            ILogMessage message = Substitute.For<ILogMessage>();\n            logSplitter.Log(message);\n            logger1.Received().Log(message);\n            logger2.Received().Log(message);\n        }\n\n        [Fact]\n        public void TestLog__MessageNull()\n        {\n            LogSplitter logSplitter = new LogSplitter(Substitute.For<IBasicLogger>(), Substitute.For<IBasicLogger>());\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                logSplitter.Log(null);\n            });\n\n            Assert.Equal(\"message\", ex.ParamName);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Logging/PrefixLoggerTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing UnityEngine;\nusing ModuleManager.Logging;\n\nnamespace ModuleManagerTests.Logging\n{\n    public class PrefixLoggerTest\n    {\n        private readonly IBasicLogger innerLogger = Substitute.For<IBasicLogger>();\n        private readonly PrefixLogger logger;\n\n        public PrefixLoggerTest()\n        {\n            logger = new PrefixLogger(\"MyMod\", innerLogger);\n        }\n\n        [Fact]\n        public void TestConstructor__PrefixNull()\n        {\n            ArgumentNullException e = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new PrefixLogger(null, innerLogger);\n            });\n\n            Assert.Equal(\"prefix\", e.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__PrefixBlank()\n        {\n            ArgumentNullException e = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new PrefixLogger(\"\", innerLogger);\n            });\n\n            Assert.Equal(\"prefix\", e.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__LoggerNull()\n        {\n            ArgumentNullException e = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new PrefixLogger(\"blah\", null);\n            });\n\n            Assert.Equal(\"logger\", e.ParamName);\n        }\n\n        [Fact]\n        public void TestLog()\n        {\n            ILogMessage logMessage = Substitute.For<ILogMessage>();\n            logMessage.LogType.Returns(LogType.Log);\n            logMessage.Message.Returns(\"well hi there\");\n            logMessage.Timestamp.Returns(new DateTime(2000, 1, 1, 12, 34, 45, 678));\n\n            logger.Log(logMessage);\n\n            innerLogger.Received().Log(Arg.Is<ILogMessage>(msg =>\n                msg.LogType == LogType.Log &&\n                msg.Timestamp == logMessage.Timestamp &&\n                msg.Message == \"[MyMod] well hi there\"\n            ));\n        }\n\n        [Fact]\n        public void TestLog__Null()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                logger.Log(null);\n            });\n\n            Assert.Equal(\"message\", ex.ParamName);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Logging/QueueLogRunnerTest.cs",
    "content": "using System;\nusing Xunit;\nusing NSubstitute;\n\nusing ModuleManager.Collections;\nusing ModuleManager.Logging;\n\nnamespace ModuleManagerTests.Logging\n{\n    public class QueueLogRunnerTest\n    {\n        [Fact]\n        public void TestConstructor__LogQueueNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new QueueLogRunner(null);\n            });\n\n            Assert.Equal(\"logQueue\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__TimeToWaitForLogsMsNegative()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new QueueLogRunner(Substitute.For<IMessageQueue<ILogMessage>>(), -1);\n            });\n\n            Assert.Contains(\"must be non-negative\", ex.Message);\n            Assert.Equal(\"timeToWaitForLogsMs\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestRun()\n        {\n            ILogMessage message1 = Substitute.For<ILogMessage>();\n            ILogMessage message2 = Substitute.For<ILogMessage>();\n            ILogMessage message3 = Substitute.For<ILogMessage>();\n            ILogMessage message4 = Substitute.For<ILogMessage>();\n            ILogMessage message5 = Substitute.For<ILogMessage>();\n            ILogMessage message6 = Substitute.For<ILogMessage>();\n            IMessageQueue<ILogMessage> messageQueue = Substitute.For<IMessageQueue<ILogMessage>>();\n            QueueLogRunner logRunner = new QueueLogRunner(messageQueue, 0);\n            int counter = 0;\n            messageQueue.TakeAll().Returns(delegate\n            {\n                IMessageQueue<ILogMessage> messageQueue2 = Substitute.For<IMessageQueue<ILogMessage>>();\n                if (counter == 0)\n                {\n                    messageQueue2.GetEnumerator().Returns(new ArrayEnumerator<ILogMessage>(message1, message2));\n                }\n                else if (counter == 1)\n                {\n                    logRunner.RequestStop(); // Called from Running state\n                    messageQueue2.GetEnumerator().Returns(new ArrayEnumerator<ILogMessage>(message3, message4));\n                }\n                else\n                {\n                    logRunner.RequestStop(); // Called from StopRequested state\n                    messageQueue2.GetEnumerator().Returns(new ArrayEnumerator<ILogMessage>(message5, message6));\n                }\n                counter++;\n                return messageQueue2;\n            });\n\n            IBasicLogger logger = Substitute.For<IBasicLogger>();\n\n            logRunner.Run(logger);\n\n            logRunner.RequestStop(); // Called from Stopped state\n\n            Received.InOrder(delegate\n            {\n                logger.Log(message1);\n                logger.Log(message2);\n                logger.Log(message3);\n                logger.Log(message4);\n                logger.Log(message5);\n                logger.Log(message6);\n            });\n        }\n\n        [Fact]\n        public void TestRun__AlreadyStarted()\n        {\n            IMessageQueue<ILogMessage> messageQueue = Substitute.For<IMessageQueue<ILogMessage>>();\n            QueueLogRunner logRunner = new QueueLogRunner(messageQueue, 0);\n            int counter = 0;\n            messageQueue.TakeAll().Returns(delegate\n            {\n                IMessageQueue<ILogMessage> messageQueue2 = Substitute.For<IMessageQueue<ILogMessage>>();\n                if (counter == 0)\n                {\n                    InvalidOperationException ex = Assert.Throws<InvalidOperationException>(delegate\n                    {\n                        logRunner.Run(Substitute.For<IBasicLogger>());\n                    });\n                    Assert.Equal(\"Cannot run from Running state\", ex.Message);\n                    logRunner.RequestStop();\n                    messageQueue2.GetEnumerator().Returns(new ArrayEnumerator<ILogMessage>());\n                }\n                else\n                {\n                    InvalidOperationException ex = Assert.Throws<InvalidOperationException>(delegate\n                    {\n                        logRunner.Run(Substitute.For<IBasicLogger>());\n                    });\n                    Assert.Equal(\"Cannot run from StopRequested state\", ex.Message);\n                    logRunner.RequestStop();\n                    messageQueue2.GetEnumerator().Returns(new ArrayEnumerator<ILogMessage>());\n                }\n                counter++;\n                return messageQueue2;\n            });\n\n            IBasicLogger logger = Substitute.For<IBasicLogger>();\n            logRunner.Run(logger);\n\n            InvalidOperationException ex2 = Assert.Throws<InvalidOperationException>(delegate\n            {\n                logRunner.Run(Substitute.For<IBasicLogger>());\n            });\n            Assert.Equal(\"Cannot run from Stopped state\", ex2.Message);\n        }\n\n        [Fact]\n        public void TestRun__LoggerNull()\n        {\n            QueueLogRunner logRunner = new QueueLogRunner(Substitute.For<IMessageQueue<ILogMessage>>());\n\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                logRunner.Run(null);\n            });\n            Assert.Equal(\"logger\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestRequestStop__NotStarted()\n        {\n            QueueLogRunner logRunner = new QueueLogRunner(Substitute.For<IMessageQueue<ILogMessage>>());\n\n            InvalidOperationException ex = Assert.Throws<InvalidOperationException>(delegate\n            {\n                logRunner.RequestStop();\n            });\n            Assert.Equal(\"Cannot request stop from Initialized state\", ex.Message);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Logging/QueueLoggerTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing ModuleManager.Collections;\nusing ModuleManager.Logging;\n\nnamespace ModuleManagerTests.Logging\n{\n    public class QueueLoggerTest\n    {\n        private readonly IMessageQueue<ILogMessage> queue = Substitute.For<IMessageQueue<ILogMessage>>();\n        private readonly QueueLogger logger;\n\n        public QueueLoggerTest()\n        {\n            logger = new QueueLogger(queue);\n        }\n\n        [Fact]\n        public void TestConstructor__QueueNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new QueueLogger(null);\n            });\n\n            Assert.Equal(\"queue\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestLog()\n        {\n            ILogMessage message = Substitute.For<ILogMessage>();\n            logger.Log(message);\n            queue.Received().Add(message);\n        }\n\n        [Fact]\n        public void TestLog__MessageNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                logger.Log(null);\n            });\n\n            Assert.Equal(\"message\", ex.ParamName);\n        }\n        \n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Logging/StreamLoggerTest.cs",
    "content": "﻿using System;\nusing System.IO;\nusing Xunit;\nusing NSubstitute;\nusing ModuleManager.Logging;\n\nnamespace ModuleManagerTests.Logging\n{\n    public class StreamLoggerTest\n    {\n        [Fact]\n        public void TestConstructor__StreamNull()\n        {\n            Assert.Throws<ArgumentNullException>(delegate\n            {\n                new StreamLogger(null);\n            });\n        }\n\n        [Fact]\n        public void TestConstructor__CantWrite()\n        {\n            using (MemoryStream stream = new MemoryStream(new byte[0], false))\n            {\n                Assert.Throws<ArgumentException>(delegate\n                {\n                    new StreamLogger(stream);\n                });\n            }\n        }\n\n        [Fact]\n        public void TestLog__AlreadyDisposed()\n        {\n            using (MemoryStream stream = new MemoryStream(new byte[0], true))\n            {\n                StreamLogger streamLogger = new StreamLogger(stream);\n                streamLogger.Dispose();\n\n                InvalidOperationException ex = Assert.Throws<InvalidOperationException>(delegate\n                {\n                    streamLogger.Log(Substitute.For<ILogMessage>());\n                });\n\n                Assert.Contains(\"Object has already been disposed\", ex.Message);\n            }\n        }\n\n        [Fact]\n        public void TestLog()\n        {\n            ILogMessage message = Substitute.For<ILogMessage>();\n            message.ToLogString().Returns(\"[OMG wtf] bbq\");\n            byte[] bytes = new byte[15];\n            using (MemoryStream stream = new MemoryStream(bytes, true))\n            {\n                using (StreamLogger streamLogger = new StreamLogger(stream))\n                {\n                    streamLogger.Log(message);\n                }\n            }\n\n            using (MemoryStream stream = new MemoryStream(bytes, false))\n            {\n                using (StreamReader reader = new StreamReader(stream))\n                {\n                    string result = reader.ReadToEnd().Trim('\\r', '\\n', '\\0');\n                    Assert.Equal(\"[OMG wtf] bbq\", result);\n                }\n            }\n        }\n\n        [Fact]\n        public void TestLog__MessageNull()\n        {\n            using (MemoryStream stream = new MemoryStream(new byte[0], true))\n            {\n                StreamLogger streamLogger = new StreamLogger(stream);\n\n                ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n                {\n                    streamLogger.Log(null);\n                });\n\n                Assert.Equal(\"message\", ex.ParamName);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Logging/UnityLoggerTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing UnityEngine;\nusing ModuleManager.Extensions;\nusing ModuleManager.Logging;\n\nnamespace ModuleManagerTests.Logging\n{\n    public class UnityLoggerTest\n    {\n        private readonly ILogger innerLogger = Substitute.For<ILogger>();\n        private readonly UnityLogger logger;\n\n        public UnityLoggerTest()\n        {\n            logger = new UnityLogger(innerLogger);\n        }\n\n        [Fact]\n        public void TestConstructor__LoggerNull()\n        {\n            ArgumentNullException e = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new UnityLogger(null);\n            });\n\n            Assert.Equal(\"logger\", e.ParamName);\n        }\n\n        [Fact]\n        public void TestLog__Info()\n        {\n            logger.Info(\"well hi there\");\n\n            innerLogger.Received().Log(LogType.Log, \"well hi there\");\n        }\n\n        [Fact]\n        public void TestLog__Warning()\n        {\n            logger.Warning(\"I'm warning you\");\n\n            innerLogger.Received().Log(LogType.Warning, \"I'm warning you\");\n        }\n\n        [Fact]\n        public void TestLog__MessageNull()\n        {\n            ArgumentNullException e = Assert.Throws<ArgumentNullException>(delegate\n            {\n                logger.Log(null);\n            });\n\n            Assert.Equal(\"message\", e.ParamName);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/LoggingAssertionHelpers.cs",
    "content": "﻿using System;\nusing UnityEngine;\nusing NSubstitute;\nusing ModuleManager.Logging;\n\nnamespace ModuleManagerTests\n{\n    public static class LoggingAssertionHelpers\n    {\n        public static void AssertInfo(this IBasicLogger logger, string message)\n        {\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n            logger.Received().Log(Arg.Is<ILogMessage>(msg => msg.LogType == LogType.Log && msg.Message == message));\n        }\n\n        public static void AssertNoInfo(this IBasicLogger logger)\n        {\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n            logger.DidNotReceive().Log(Arg.Is<ILogMessage>(msg => msg.LogType == LogType.Log));\n        }\n\n        public static void AssertWarning(this IBasicLogger logger, string message)\n        {\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n            logger.Received().Log(Arg.Is<ILogMessage>(msg => msg.LogType == LogType.Warning && msg.Message == message));\n        }\n\n        public static void AssertNoWarning(this IBasicLogger logger)\n        {\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n            logger.DidNotReceive().Log(Arg.Is<ILogMessage>(msg => msg.LogType == LogType.Warning));\n        }\n\n        public static void AssertError(this IBasicLogger logger, string message)\n        {\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n            logger.Received().Log(Arg.Is<ILogMessage>(msg => msg.LogType == LogType.Error && msg.Message == message));\n        }\n\n        public static void AssertNoError(this IBasicLogger logger)\n        {\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n            logger.DidNotReceive().Log(Arg.Is<ILogMessage>(msg => msg.LogType == LogType.Error));\n        }\n\n        public static void AssertException(this IBasicLogger logger, string message, Exception exception)\n        {\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n            logger.Received().Log(Arg.Is<ILogMessage>(msg => msg.LogType == LogType.Exception && msg.Message == message + \": \" + exception.ToString()));\n        }\n\n        public static void AssertException(this IBasicLogger logger, Exception exception)\n        {\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n            logger.Received().Log(Arg.Is<ILogMessage>(msg => msg.LogType == LogType.Exception && msg.Message == exception.ToString()));\n        }\n\n        public static void AssertNoException(this IBasicLogger logger)\n        {\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n            logger.DidNotReceive().Log(Arg.Is<ILogMessage>(msg => msg.LogType == LogType.Exception));\n        }\n\n        public static void AssertNoLog(this IBasicLogger logger)\n        {\n            if (logger == null) throw new ArgumentNullException(nameof(logger));\n            logger.DidNotReceiveWithAnyArgs().Log(null);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/MMPatchLoaderTest.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing Xunit;\nusing NSubstitute;\nusing UnityEngine;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Logging;\nusing ModuleManager.Progress;\nusing NodeStack = ModuleManager.Collections.ImmutableStack<ConfigNode>;\n\nnamespace ModuleManagerTests\n{\n    // This is not intended to fully test ModifyNode, however it is useful to include tests for bugfixes here before it is split up\n    public class MMPatchLoaderTest\n    {\n        private readonly IBasicLogger logger = Substitute.For<IBasicLogger>();\n        private readonly IPatchProgress progress = Substitute.For<IPatchProgress>();\n\n        [Fact]\n        public void TestModifyNode__IndexAllWithAssign()\n        {\n            ConfigNode c1 = new TestConfigNode(\"NODE\")\n            {\n                { \"foo\", \"bar1\" },\n                { \"foo\", \"bar2\" },\n            };\n\n            UrlDir.UrlConfig c2u = UrlBuilder.CreateConfig(\"abc/def\", new TestConfigNode(\"@NODE\")\n            {\n                { \"@foo,*\", \"bar3\" },\n            });\n            \n            PatchContext context = new PatchContext(c2u, Enumerable.Empty<IProtoUrlConfig>(), logger, progress);\n\n            ConfigNode c3 = MMPatchLoader.ModifyNode(new NodeStack(c1), c2u.config, context);\n\n            EnsureNoErrors();\n\n            AssertConfigNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"foo\", \"bar3\" },\n                { \"foo\", \"bar3\" },\n            }, c3);\n        }\n\n        [Fact]\n        public void TestModifyNode__MultiplyValue()\n        {\n            ConfigNode c1 = new TestConfigNode(\"NODE\")\n            {\n                { \"foo\", \"3\" },\n                { \"foo\", \"5\" },\n            };\n\n            UrlDir.UrlConfig c2u = UrlBuilder.CreateConfig(\"abc/def\", new TestConfigNode(\"@NODE\")\n            {\n                { \"@foo *\", \"2\" },\n            });\n\n            PatchContext context = new PatchContext(c2u, Enumerable.Empty<IProtoUrlConfig>(), logger, progress);\n\n            ConfigNode c3 = MMPatchLoader.ModifyNode(new NodeStack(c1), c2u.config, context);\n\n            EnsureNoErrors();\n\n            AssertConfigNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"foo\", \"6\" },\n                { \"foo\", \"5\" },\n            }, c3);\n        }\n\n        [Fact]\n        public void TestModifyNode__EditNode__SpecialCharacters()\n        {\n            ConfigNode c1 = new TestConfigNode(\"NODE\")\n            {\n                new TestConfigNode(\"INNER_NODE\")\n                {\n                    { \"weird_values\", \"some\\r\\n\\tstuff\" },\n                },\n            };\n\n            UrlDir.UrlConfig c2u = UrlBuilder.CreateConfig(\"abc/def\", new TestConfigNode(\"@NODE\")\n            {\n                new TestConfigNode(\"@INNER_NODE\")\n                {\n                    { \"another_weird_value\", \"some\\r\\nmore\\tstuff\" },\n                },\n            });\n\n            PatchContext context = new PatchContext(c2u, Enumerable.Empty<IProtoUrlConfig>(), logger, progress);\n\n            ConfigNode c3 = MMPatchLoader.ModifyNode(new NodeStack(c1), c2u.config, context);\n\n            EnsureNoErrors();\n\n            AssertConfigNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                new TestConfigNode(\"INNER_NODE\")\n                {\n                    { \"weird_values\", \"some\\r\\n\\tstuff\" },\n                    { \"another_weird_value\", \"some\\r\\nmore\\tstuff\" },\n                },\n            }, c3);\n        }\n\n        [Fact]\n        public void TestModifyNode__ReplaceNode__SpecialCharacters()\n        {\n            ConfigNode c1 = new TestConfigNode(\"NODE\")\n            {\n                new TestConfigNode(\"INNER_NODE\")\n                {\n                    { \"weird_values\", \"some\\r\\n\\tstuff\" },\n                },\n            };\n\n            UrlDir.UrlConfig c2u = UrlBuilder.CreateConfig(\"abc/def\", new TestConfigNode(\"@NODE\")\n            {\n                new TestConfigNode(\"%INNER_NODE\")\n                {\n                    { \"another_weird_value\", \"some\\r\\nmore\\tstuff\" },\n                },\n                new TestConfigNode(\"%OTHER_INNER_NODE\")\n                {\n                    { \"another_weirder_value\", \"even\\r\\nmore\\tstuff\" },\n                },\n            });\n\n            PatchContext context = new PatchContext(c2u, Enumerable.Empty<IProtoUrlConfig>(), logger, progress);\n\n            ConfigNode c3 = MMPatchLoader.ModifyNode(new NodeStack(c1), c2u.config, context);\n\n            EnsureNoErrors();\n\n            AssertConfigNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                new TestConfigNode(\"INNER_NODE\")\n                {\n                    { \"weird_values\", \"some\\r\\n\\tstuff\" },\n                    { \"another_weird_value\", \"some\\r\\nmore\\tstuff\" },\n                },\n                new TestConfigNode(\"OTHER_INNER_NODE\")\n                {\n                    { \"another_weirder_value\", \"even\\r\\nmore\\tstuff\" },\n                },\n            }, c3);\n        }\n\n        private void AssertConfigNodesEqual(ConfigNode expected, ConfigNode observed)\n        {\n            Assert.Equal(expected.ToString(), observed.ToString());\n        }\n\n        private void EnsureNoErrors()\n        {\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null, null);\n\n            logger.AssertNoWarning();\n            logger.AssertNoError();\n            logger.AssertNoException();\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/ModuleManagerTests.csproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Import Project=\"..\\packages\\xunit.runner.visualstudio.2.4.3\\build\\net452\\xunit.runner.visualstudio.props\" Condition=\"Exists('..\\packages\\xunit.runner.visualstudio.2.4.3\\build\\net452\\xunit.runner.visualstudio.props')\" />\n  <Import Project=\"..\\packages\\xunit.runner.console.2.4.1\\build\\xunit.runner.console.props\" Condition=\"Exists('..\\packages\\xunit.runner.console.2.4.1\\build\\xunit.runner.console.props')\" />\n  <Import Project=\"..\\packages\\xunit.core.2.4.1\\build\\xunit.core.props\" Condition=\"Exists('..\\packages\\xunit.core.2.4.1\\build\\xunit.core.props')\" />\n  <Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" />\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n    <ProjectGuid>{BC2A08C8-64EF-4823-A40B-8889C1CCFD75}</ProjectGuid>\n    <OutputType>Library</OutputType>\n    <AppDesignerFolder>Properties</AppDesignerFolder>\n    <RootNamespace>ModuleManagerTests</RootNamespace>\n    <AssemblyName>ModuleManagerTests</AssemblyName>\n    <TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>\n    <FileAlignment>512</FileAlignment>\n    <NuGetPackageImportStamp>\n    </NuGetPackageImportStamp>\n    <TargetFrameworkProfile />\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    <DebugSymbols>true</DebugSymbols>\n    <DebugType>full</DebugType>\n    <Optimize>false</Optimize>\n    <OutputPath>bin\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n    <Prefer32Bit>false</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    <DebugType>pdbonly</DebugType>\n    <Optimize>true</Optimize>\n    <OutputPath>bin\\Release\\</OutputPath>\n    <DefineConstants>TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n    <Prefer32Bit>false</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup>\n    <LangVersion>8.0</LangVersion>\n  </PropertyGroup>\n  <ItemGroup>\n    <Reference Include=\"Assembly-CSharp\" />\n    <Reference Include=\"Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\Castle.Core.4.4.1\\lib\\net45\\Castle.Core.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Microsoft.CSharp\" />\n    <Reference Include=\"NSubstitute, Version=4.2.0.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\NSubstitute.4.2.2\\lib\\net46\\NSubstitute.dll</HintPath>\n    </Reference>\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Configuration\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Runtime.CompilerServices.Unsafe, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\System.Runtime.CompilerServices.Unsafe.5.0.0\\lib\\net45\\System.Runtime.CompilerServices.Unsafe.dll</HintPath>\n    </Reference>\n    <Reference Include=\"System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\System.Threading.Tasks.Extensions.4.5.4\\lib\\net461\\System.Threading.Tasks.Extensions.dll</HintPath>\n    </Reference>\n    <Reference Include=\"System.Xml.Linq\" />\n    <Reference Include=\"System.Data.DataSetExtensions\" />\n    <Reference Include=\"System.Data\" />\n    <Reference Include=\"System.Xml\" />\n    <Reference Include=\"UnityEngine\" />\n    <Reference Include=\"UnityEngine.CoreModule\" />\n    <Reference Include=\"xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\xunit.abstractions.2.0.3\\lib\\net35\\xunit.abstractions.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"xunit.assert, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\xunit.assert.2.4.1\\lib\\netstandard1.1\\xunit.assert.dll</HintPath>\n    </Reference>\n    <Reference Include=\"xunit.core, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\xunit.extensibility.core.2.4.1\\lib\\net452\\xunit.core.dll</HintPath>\n    </Reference>\n    <Reference Include=\"xunit.execution.desktop, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\xunit.extensibility.execution.2.4.1\\lib\\net452\\xunit.execution.desktop.dll</HintPath>\n    </Reference>\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"Collections\\ArrayEnumeratorTest.cs\" />\n    <Compile Include=\"Collections\\ImmutableStackTest.cs\" />\n    <Compile Include=\"Collections\\KeyValueCacheTest.cs\" />\n    <Compile Include=\"Collections\\MessageQueueTest.cs\" />\n    <Compile Include=\"CommandParserTest.cs\" />\n    <Compile Include=\"DummyTest.cs\" />\n    <Compile Include=\"Extensions\\ByteArrayExtensionsTest.cs\" />\n    <Compile Include=\"Extensions\\ConfigNodeExtensionsTest.cs\" />\n    <Compile Include=\"Extensions\\IBasicLoggerExtensionsTest.cs\" />\n    <Compile Include=\"Extensions\\NodeStackExtensionsTest.cs\" />\n    <Compile Include=\"Extensions\\StringExtensionsTest.cs\" />\n    <Compile Include=\"Extensions\\UrlConfigExtensionsTest.cs\" />\n    <Compile Include=\"Extensions\\UrlDirExtensionsTest.cs\" />\n    <Compile Include=\"Extensions\\UrlFileExtensionsTest.cs\" />\n    <Compile Include=\"InGameTestRunnerTest.cs\" />\n    <Compile Include=\"LoggingAssertionHelpers.cs\" />\n    <Compile Include=\"Logging\\StreamLoggerTest.cs\" />\n    <Compile Include=\"Logging\\UnityLoggerTest.cs\" />\n    <Compile Include=\"Logging\\LogSplitterTest.cs\" />\n    <Compile Include=\"Logging\\LogMessageTest.cs\" />\n    <Compile Include=\"Logging\\PrefixLoggerTest.cs\" />\n    <Compile Include=\"Logging\\QueueLoggerTest.cs\" />\n    <Compile Include=\"Logging\\QueueLogRunnerTest.cs\" />\n    <Compile Include=\"MMPatchLoaderTest.cs\" />\n    <Compile Include=\"NeedsCheckerTest.cs\" />\n    <Compile Include=\"NodeMatcherTest.cs\" />\n    <Compile Include=\"OperatorParserTest.cs\" />\n    <Compile Include=\"PassTest.cs\" />\n    <Compile Include=\"PatchApplierTest.cs\" />\n    <Compile Include=\"Patches\\CopyPatchTest.cs\" />\n    <Compile Include=\"Patches\\DeletePatchTest.cs\" />\n    <Compile Include=\"Patches\\EditPatchTest.cs\" />\n    <Compile Include=\"Patches\\InsertPatchTest.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\InsertPassSpecifierTest.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\FinalPassSpecifierTest.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\AfterPassSpecifierTest.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\ForPassSpecifierTest.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\BeforePassSpecifierTest.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\LegacyPassSpecifierTest.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\FirstPassSpecifierTest.cs\" />\n    <Compile Include=\"Patches\\PassSpecifiers\\LastPassSpecifierTest.cs\" />\n    <Compile Include=\"Patches\\PatchCompilerTest.cs\" />\n    <Compile Include=\"Patches\\ProtoPatchBuilderTest.cs\" />\n    <Compile Include=\"PatchListTest.cs\" />\n    <Compile Include=\"ProtoUrlConfigTest.cs\" />\n    <Compile Include=\"PatchExtractorTest.cs\" />\n    <Compile Include=\"Properties\\AssemblyInfo.cs\" />\n    <Compile Include=\"Progress\\PatchProgressTest.cs\" />\n    <Compile Include=\"Tags\\TagListParserTest.cs\" />\n    <Compile Include=\"Tags\\TagListTest.cs\" />\n    <Compile Include=\"Tags\\TagTest.cs\" />\n    <Compile Include=\"Threading\\TaskStatusTest.cs\" />\n    <Compile Include=\"Threading\\BackgroundTaskTest.cs\" />\n    <Compile Include=\"Utils\\CounterTest.cs\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"app.config\" />\n    <None Include=\"packages.config\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Service Include=\"{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\ModuleManager\\ModuleManager.csproj\">\n      <Project>{02c8e3af-69f9-4102-ab60-dd6de60662d3}</Project>\n      <Name>ModuleManager</Name>\n    </ProjectReference>\n    <ProjectReference Include=\"..\\TestUtils\\TestUtils.csproj\">\n      <Project>{20eaafe6-510d-4374-8d2f-6b52d0178e85}</Project>\n      <Name>TestUtils</Name>\n    </ProjectReference>\n  </ItemGroup>\n  <ItemGroup>\n    <Analyzer Include=\"..\\packages\\xunit.analyzers.0.10.0\\analyzers\\dotnet\\cs\\xunit.analyzers.dll\" />\n  </ItemGroup>\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n  <Target Name=\"EnsureNuGetPackageBuildImports\" BeforeTargets=\"PrepareForBuild\">\n    <PropertyGroup>\n      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>\n    </PropertyGroup>\n    <Error Condition=\"!Exists('..\\packages\\xunit.core.2.4.1\\build\\xunit.core.props')\" Text=\"$([System.String]::Format('$(ErrorText)', '..\\packages\\xunit.core.2.4.1\\build\\xunit.core.props'))\" />\n    <Error Condition=\"!Exists('..\\packages\\xunit.core.2.4.1\\build\\xunit.core.targets')\" Text=\"$([System.String]::Format('$(ErrorText)', '..\\packages\\xunit.core.2.4.1\\build\\xunit.core.targets'))\" />\n    <Error Condition=\"!Exists('..\\packages\\xunit.runner.console.2.4.1\\build\\xunit.runner.console.props')\" Text=\"$([System.String]::Format('$(ErrorText)', '..\\packages\\xunit.runner.console.2.4.1\\build\\xunit.runner.console.props'))\" />\n    <Error Condition=\"!Exists('..\\packages\\xunit.runner.visualstudio.2.4.3\\build\\net452\\xunit.runner.visualstudio.props')\" Text=\"$([System.String]::Format('$(ErrorText)', '..\\packages\\xunit.runner.visualstudio.2.4.3\\build\\net452\\xunit.runner.visualstudio.props'))\" />\n  </Target>\n  <Import Project=\"..\\packages\\xunit.core.2.4.1\\build\\xunit.core.targets\" Condition=\"Exists('..\\packages\\xunit.core.2.4.1\\build\\xunit.core.targets')\" />\n</Project>"
  },
  {
    "path": "ModuleManagerTests/NeedsCheckerTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Logging;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests\n{\n    public class NeedsCheckerTest\n    {\n        private readonly UrlDir gameData;\n\n        private readonly IPatchProgress progress = Substitute.For<IPatchProgress>();\n        private readonly IBasicLogger logger = Substitute.For<IBasicLogger>();\n        private readonly NeedsChecker needsChecker;\n\n        public NeedsCheckerTest()\n        {\n            gameData = UrlBuilder.CreateGameData();\n            needsChecker = new NeedsChecker(new[] { \"mod1\", \"mod2\", \"mod/2\" }, gameData, progress, logger);\n        }\n\n        [Fact]\n        public void TestConstructor__ModsNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new NeedsChecker(null, gameData, progress, logger);\n            });\n\n            Assert.Equal(\"mods\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__GameDataNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new NeedsChecker(new string[0], null, progress, logger);\n            });\n\n            Assert.Equal(\"gameData\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__ProgressNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new NeedsChecker(new string[0], gameData, null, logger);\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__LoggerNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new NeedsChecker(new string[0], gameData, progress, null);\n            });\n\n            Assert.Equal(\"logger\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestCheckNeedsExpression()\n        {\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod1\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod2\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"mod3\"));\n        }\n\n        [Fact]\n        public void TestCheckNeedsExpression__AndOr()\n        {\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod1&mod2\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod1,mod2\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod1|mod2\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod1|mod3\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod1&mod2|mod3\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod1,mod2|mod3\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod1|mod3&mod2\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod1|mod3,mod2\"));\n\n            Assert.False(needsChecker.CheckNeedsExpression(\"mod1&mod3\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"mod1,mod3\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"mod1&mod2&mod3\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"mod1,mod2,mod3\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"mod3|mod4\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"mod1|mod2&mod3\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"mod1|mod2,mod3\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"mod3&mod1|mod2\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"mod3,mod1|mod2\"));\n        }\n\n        [Fact]\n        public void TestCheckNeedsExpression__Not()\n        {\n            Assert.True(needsChecker.CheckNeedsExpression(\"!mod3\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod1,!mod3\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"!mod1|!mod3\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod1|!mod2\"));\n            \n            Assert.False(needsChecker.CheckNeedsExpression(\"!mod1\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"!mod1,mod2\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"!mod1&!mod3\"));\n        }\n\n        [Fact]\n        public void TestCheckNeedsExpression__Capitalization()\n        {\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod1\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"Mod1\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"MOD1\"));\n\n            Assert.False(needsChecker.CheckNeedsExpression(\"mod3\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"Mod3\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"MOD3\"));\n        }\n\n        [Fact]\n        public void TestCheckNeedsExpression__Directory()\n        {\n            UrlBuilder.CreateDir(\"abc\", gameData);\n            UrlBuilder.CreateDir(\"ghi/jkl\", gameData);\n\n            Assert.True(needsChecker.CheckNeedsExpression(\"/abc\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"abc/\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"/abc/\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"ghi/jkl\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"/ghi/jkl\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"ghi/jkl/\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod1&ghi/jkl\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod3|ghi/jkl\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"abc/&ghi/jkl\"));\n            Assert.True(needsChecker.CheckNeedsExpression(\"mod/2\"));\n\n            Assert.False(needsChecker.CheckNeedsExpression(\"abc\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"mod3&ghi/jkl\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"Ghi/jkl\"));\n            Assert.False(needsChecker.CheckNeedsExpression(\"mno/pqr\"));\n        }\n\n        [Fact]\n        public void TestCheckNeedsExpression__Null()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                needsChecker.CheckNeedsExpression(null);\n            });\n\n            Assert.Equal(\"needsExpression\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestCheckNeedsExpression__Empty()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                needsChecker.CheckNeedsExpression(\"\");\n            });\n\n            Assert.Equal(\"needsExpression\", ex.ParamName);\n            Assert.Contains(\"can't be empty\", ex.Message);\n        }\n\n        [Fact]\n        public void TestCheckNeeds()\n        {\n            UrlBuilder.CreateDir(\"ghi/jkl\", gameData);\n\n            Assert.True(needsChecker.CheckNeeds(\"mod1\"));\n            Assert.True(needsChecker.CheckNeeds(\"MOD1\"));\n            Assert.True(needsChecker.CheckNeeds(\"mod2\"));\n\n            Assert.False(needsChecker.CheckNeeds(\"mod1&mod2\"));\n            Assert.False(needsChecker.CheckNeeds(\"ghi/jkl\"));\n        }\n\n        [Fact]\n        public void TestCheckNeeds__Null()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                needsChecker.CheckNeeds(null);\n            });\n\n            Assert.Equal(\"mod\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__Empty()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                needsChecker.CheckNeeds(\"\");\n            });\n\n            Assert.Equal(\"mod\", ex.ParamName);\n            Assert.Contains(\"can't be empty\", ex.Message);\n        }\n\n        [Fact]\n        public void TestCheckNeedsRecursive()\n        {\n            ConfigNode node = new TestConfigNode(\"SOME_NODE\")\n            {\n                { \"aa\", \"00\" },\n                { \"bb:NEEDS[mod1]\", \"01\" },\n                { \"cc:NEEDS[mod3]\", \"02\" },\n                new TestConfigNode(\"INNER_NODE_1\")\n                {\n                    { \"dd\", \"03\" },\n                    { \"ee\", \"04\" },\n                    new TestConfigNode(\"INNER_INNER_NODE_1\")\n                    {\n                        { \"ff\", \"05\" },\n                    },\n                },\n                new TestConfigNode(\"INNER_NODE_2\")\n                {\n                    { \"gg:NEEDS[mod1]\", \"06\" },\n                    { \"hh:NEEDS[mod3]\", \"07\" },\n                    { \"ii\", \"08\" },\n                    new TestConfigNode(\"INNER_INNER_NODE_11\")\n                    {\n                        { \"jj\", \"09\" },\n                    },\n                    new TestConfigNode(\"INNER_INNER_NODE_12:NEEDS[mod2]\")\n                    {\n                        { \"kk\", \"10\" },\n                    },\n                    new TestConfigNode(\"INNER_INNER_NODE_12:NEEDS[mod3]\")\n                    {\n                        { \"ll\", \"11\" },\n                    },\n                },\n                new TestConfigNode(\"INNER_NODE_3:NEEDS[mod1]\")\n                {\n                    { \"mm:NEEDS[mod1]\", \"12\" },\n                    { \"nn:NEEDS[mod3]\", \"13\" },\n                    { \"oo\", \"14\" },\n                    new TestConfigNode(\"INNER_INNER_NODE_21\")\n                    {\n                        { \"pp\", \"15\" },\n                    },\n                    new TestConfigNode(\"INNER_INNER_NODE_22:NEEDS[mod2]\")\n                    {\n                        { \"qq\", \"16\" },\n                    },\n                    new TestConfigNode(\"INNER_INNER_NODE_22:NEEDS[mod3]\")\n                    {\n                        { \"rr\", \"17\" },\n                    },\n                },\n                new TestConfigNode(\"INNER_NODE_4:NEEDS[mod3]\")\n                {\n                    { \"ss:NEEDS[mod1]\", \"18\" },\n                },\n            };\n\n            UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def\", node);\n\n            needsChecker.CheckNeedsRecursive(node, urlConfig);\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null, null);\n\n            Received.InOrder(delegate\n            {\n                progress.NeedsUnsatisfiedValue(urlConfig, \"SOME_NODE/cc:NEEDS[mod3]\");\n                progress.NeedsUnsatisfiedValue(urlConfig, \"SOME_NODE/INNER_NODE_2/hh:NEEDS[mod3]\");\n                progress.NeedsUnsatisfiedNode(urlConfig, \"SOME_NODE/INNER_NODE_2/INNER_INNER_NODE_12:NEEDS[mod3]\");\n                progress.NeedsUnsatisfiedValue(urlConfig, \"SOME_NODE/INNER_NODE_3/nn:NEEDS[mod3]\");\n                progress.NeedsUnsatisfiedNode(urlConfig, \"SOME_NODE/INNER_NODE_3/INNER_INNER_NODE_22:NEEDS[mod3]\");\n                progress.NeedsUnsatisfiedNode(urlConfig, \"SOME_NODE/INNER_NODE_4:NEEDS[mod3]\");\n            });\n\n            Assert.Equal(2, node.values.Count);\n            Assert.Equal(3, node.nodes.Count);\n            \n            Assert.Equal(\"aa\", node.values[0].name);\n            Assert.Equal(\"00\", node.values[0].value);\n            \n            Assert.Equal(\"bb\", node.values[1].name);\n            Assert.Equal(\"01\", node.values[1].value);\n            \n            Assert.Same(node.nodes[0], node.nodes[0]);\n            Assert.Equal(\"INNER_NODE_1\", node.nodes[0].name);\n\n            Assert.Equal(2, node.nodes[0].values.Count);\n            Assert.Equal(1, node.nodes[0].nodes.Count);\n            \n            Assert.Equal(\"dd\", node.nodes[0].values[0].name);\n            Assert.Equal(\"03\", node.nodes[0].values[0].value);\n            \n            Assert.Equal(\"ee\", node.nodes[0].values[1].name);\n            Assert.Equal(\"04\", node.nodes[0].values[1].value);\n            \n            Assert.Equal(\"INNER_INNER_NODE_1\", node.nodes[0].nodes[0].name);\n\n            Assert.Equal(1, node.nodes[0].nodes[0].values.Count);\n            Assert.Equal(0, node.nodes[0].nodes[0].nodes.Count);\n            \n            Assert.Equal(\"ff\", node.nodes[0].nodes[0].values[0].name);\n            Assert.Equal(\"05\", node.nodes[0].nodes[0].values[0].value);\n\n            // Assert.NotSame(node.nodes[1], newNode.nodes[1]);\n            Assert.Equal(\"INNER_NODE_2\", node.nodes[1].name);\n\n            Assert.Equal(2, node.nodes[1].values.Count);\n            Assert.Equal(2, node.nodes[1].nodes.Count);\n\n            Assert.Equal(\"gg\", node.nodes[1].values[0].name);\n            Assert.Equal(\"06\", node.nodes[1].values[0].value);\n\n            Assert.Equal(\"ii\", node.nodes[1].values[1].name);\n            Assert.Equal(\"08\", node.nodes[1].values[1].value);\n\n            Assert.Equal(\"INNER_INNER_NODE_11\", node.nodes[1].nodes[0].name);\n\n            Assert.Equal(\"jj\", node.nodes[1].nodes[0].values[0].name);\n            Assert.Equal(\"09\", node.nodes[1].nodes[0].values[0].value);\n\n            Assert.Equal(\"INNER_INNER_NODE_12\", node.nodes[1].nodes[1].name);\n\n            Assert.Equal(\"kk\", node.nodes[1].nodes[1].values[0].name);\n            Assert.Equal(\"10\", node.nodes[1].nodes[1].values[0].value);\n\n            // Assert.NotSame(node.nodes[1], newNode.nodes[1]);\n            Assert.Equal(\"INNER_NODE_3\", node.nodes[2].name);\n\n            Assert.Equal(2, node.nodes[2].values.Count);\n            Assert.Equal(2, node.nodes[2].nodes.Count);\n\n            Assert.Equal(\"mm\", node.nodes[2].values[0].name);\n            Assert.Equal(\"12\", node.nodes[2].values[0].value);\n\n            Assert.Equal(\"oo\", node.nodes[2].values[1].name);\n            Assert.Equal(\"14\", node.nodes[2].values[1].value);\n\n            Assert.Equal(\"INNER_INNER_NODE_21\", node.nodes[2].nodes[0].name);\n\n            Assert.Equal(\"pp\", node.nodes[2].nodes[0].values[0].name);\n            Assert.Equal(\"15\", node.nodes[2].nodes[0].values[0].value);\n\n            Assert.Equal(\"INNER_INNER_NODE_22\", node.nodes[2].nodes[1].name);\n\n            Assert.Equal(\"qq\", node.nodes[2].nodes[1].values[0].name);\n            Assert.Equal(\"16\", node.nodes[2].nodes[1].values[0].value);\n\n        }\n        \n        [Fact]\n        public void TestCheckNeedsRecursive__NodeNull()\n        {\n            UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"NODE\"));\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                needsChecker.CheckNeedsRecursive(null, urlConfig);\n            });\n\n            Assert.Equal(\"node\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestCheckNeedsRecursive__UrlConfigNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                needsChecker.CheckNeedsRecursive(new ConfigNode(), null);\n            });\n\n            Assert.Equal(\"urlConfig\", ex.ParamName);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/NodeMatcherTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing TestUtils;\nusing ModuleManager;\n\nnamespace ModuleManagerTests\n{\n    public class NodeMatcherTest\n    {\n        #region Constructor\n\n        [Fact]\n        public void TestConstructor__TypeNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new NodeMatcher(null, null, null);\n            });\n\n            Assert.Equal(\"type\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__TypeBlank()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new NodeMatcher(\"\", null, null);\n            });\n\n            Assert.Equal(\"type\", ex.ParamName);\n            Assert.Contains(\"can't be empty\", ex.Message);\n        }\n\n        [Fact]\n        public void TestConstructor__NameBlank()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new NodeMatcher(\"NODE\", \"\", null);\n            });\n\n            Assert.Equal(\"name\", ex.ParamName);\n            Assert.Contains(\"can't be empty (null allowed)\", ex.Message);\n        }\n\n        [Fact]\n        public void TestConstructor__ConstraintsBlank()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new NodeMatcher(\"NODE\", null, \"\");\n            });\n\n            Assert.Equal(\"constraints\", ex.ParamName);\n            Assert.Contains(\"can't be empty (null allowed)\", ex.Message);\n        }\n\n        [Fact]\n        public void TestConstructor__ConstraintsNotBracketBalanced()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new NodeMatcher(\"NODE\", null, \"stuff[blah\");\n            });\n\n            Assert.Equal(\"constraints\", ex.ParamName);\n            Assert.Contains(\"is not bracket balanced: stuff[blah\", ex.Message);\n        }\n\n        #endregion\n\n        #region IsMatch\n\n        [Fact]\n        public void TestIsMatch()\n        {\n            NodeMatcher matcher = new NodeMatcher(\"NODE\", null, null);\n\n            Assert.True(matcher.IsMatch(new ConfigNode(\"NODE\")));\n            Assert.False(matcher.IsMatch(new ConfigNode(\"PART\")));\n        }\n\n        [Fact]\n        public void TestIsMatch__Name()\n        {\n            NodeMatcher matcher = new NodeMatcher(\"NODE\", \"blah\", null);\n\n            Assert.True(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blah\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new ConfigNode(\"NODE\")));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"bleh\" },\n            }));\n            \n            Assert.False(matcher.IsMatch(new ConfigNode(\"PART\")));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"PART\")\n            {\n                { \"name\", \"blah\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"PART\")\n            {\n                { \"name\", \"bleh\" },\n            }));\n        }\n\n        [Fact]\n        public void TestIsMatch__Name__Wildcard()\n        {\n            NodeMatcher matcher = new NodeMatcher(\"NODE\", \"bl*h\", null);\n\n            Assert.True(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blah\" },\n            }));\n\n            Assert.True(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blablah\" },\n            }));\n\n            Assert.True(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"bleh\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new ConfigNode(\"NODE\")));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blue\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new ConfigNode(\"PART\")));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"PART\")\n            {\n                { \"name\", \"blah\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"PART\")\n            {\n                { \"name\", \"blue\" },\n            }));\n        }\n\n        [Fact]\n        public void TestIsMatch__Name__Multiple()\n        {\n            NodeMatcher matcher = new NodeMatcher(\"NODE\", \"blah|bleh|blih*\", null);\n\n            Assert.True(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blah\" },\n            }));\n\n            Assert.True(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"bleh\" },\n            }));\n\n            Assert.True(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blih\" },\n            }));\n\n            Assert.True(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blihblih\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new ConfigNode(\"NODE\")));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"bloh\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new ConfigNode(\"PART\")));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"PART\")\n            {\n                { \"name\", \"blah\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"PART\")\n            {\n                { \"name\", \"bleh\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"PART\")\n            {\n                { \"name\", \"blih\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"PART\")\n            {\n                { \"name\", \"bloh\" },\n            }));\n        }\n\n        [Fact]\n        public void TestIsMatch__Constraints()\n        {\n            NodeMatcher matcher = new NodeMatcher(\"NODE\", \"blah\", \"@FOO[bar*],#something[else]\");\n\n            Assert.True(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blah\" },\n                { \"something\", \"else\" },\n                new TestConfigNode(\"FOO\")\n                {\n                    { \"name\", \"barbar\" },\n                },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blah\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blah\" },\n                { \"something\", \"else\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blah\" },\n                new TestConfigNode(\"FOO\")\n                {\n                    { \"name\", \"barbar\" },\n                },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"bleh\" },\n                { \"something\", \"else\" },\n                new TestConfigNode(\"FOO\")\n                {\n                    { \"name\", \"barbar\" },\n                },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NADE\")\n            {\n                { \"name\", \"blah\" },\n                { \"something\", \"else\" },\n                new TestConfigNode(\"FOO\")\n                {\n                    { \"name\", \"barbar\" },\n                },\n            }));\n        }\n\n        [Fact]\n        public void TestIsMatch__Constraints_Open()\n        {\n            NodeMatcher matcher = new NodeMatcher(\"NODE\", \"blah\", \"@FOO,#something\");\n\n            Assert.True(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blah\" },\n                { \"something\", \"else\" },\n                new TestConfigNode(\"FOO\")\n                {\n                    { \"name\", \"barbar\" },\n                },\n            }));\n\n            Assert.True(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blah\" },\n                { \"something\", \"else\" },\n                new TestConfigNode(\"FOO\")\n                {\n                    { \"name\", \"barbar\" },\n                },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blah\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blah\" },\n                { \"something\", \"else\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"blah\" },\n                new TestConfigNode(\"FOO\")\n                {\n                    { \"name\", \"barbar\" },\n                },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"bleh\" },\n                { \"something\", \"else\" },\n                new TestConfigNode(\"FOO\")\n                {\n                    { \"name\", \"barbar\" },\n                },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NADE\")\n            {\n                { \"name\", \"blah\" },\n                { \"something\", \"else\" },\n                new TestConfigNode(\"FOO\")\n                {\n                    { \"name\", \"barbar\" },\n                },\n            }));\n        }\n\n        #endregion\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/OperatorParserTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing ModuleManager;\n\nnamespace ModuleManagerTests\n{\n    public class OperatorParserTest\n    {\n        [Fact]\n        public void TestParse__Null()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                OperatorParser.Parse(null, out string _);\n            });\n\n            Assert.Equal(\"name\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestParse__Empty()\n        {\n            Operator op = OperatorParser.Parse(\"\", out string result);\n\n            Assert.Equal(Operator.Assign, op);\n            Assert.Equal(\"\", result);\n        }\n\n        [Fact]\n        public void TestParse__Assign()\n        {\n            Operator op = OperatorParser.Parse(\"some_stuff,1[2, ]\", out string result);\n\n            Assert.Equal(Operator.Assign, op);\n            Assert.Equal(\"some_stuff,1[2, ]\", result);\n        }\n\n        [Fact]\n        public void TestParse__Add()\n        {\n            Operator op = OperatorParser.Parse(\"some_stuff,1[2, ]  +\", out string result);\n\n            Assert.Equal(Operator.Add, op);\n            Assert.Equal(\"some_stuff,1[2, ]\", result);\n        }\n\n        [Fact]\n        public void TestParse__Subtract()\n        {\n            Operator op = OperatorParser.Parse(\"some_stuff,1[2, ]  -\", out string result);\n\n            Assert.Equal(Operator.Subtract, op);\n            Assert.Equal(\"some_stuff,1[2, ]\", result);\n        }\n\n        [Fact]\n        public void TestParse__Multiply()\n        {\n            Operator op = OperatorParser.Parse(\"some_stuff,1[2, ]  *\", out string result);\n\n            Assert.Equal(Operator.Multiply, op);\n            Assert.Equal(\"some_stuff,1[2, ]\", result);\n        }\n\n        [Fact]\n        public void TestParse__Divide()\n        {\n            Operator op = OperatorParser.Parse(\"some_stuff,1[2, ]  /\", out string result);\n\n            Assert.Equal(Operator.Divide, op);\n            Assert.Equal(\"some_stuff,1[2, ]\", result);\n        }\n\n        [Fact]\n        public void TestParse__Exponentiate()\n        {\n            Operator op = OperatorParser.Parse(\"some_stuff,1[2, ]  !\", out string result);\n\n            Assert.Equal(Operator.Exponentiate, op);\n            Assert.Equal(\"some_stuff,1[2, ]\", result);\n        }\n\n        [Fact]\n        public void TestParse__RegexReplace()\n        {\n            Operator op = OperatorParser.Parse(\"some_stuff,1[2, ]  ^\", out string result);\n\n            Assert.Equal(Operator.RegexReplace, op);\n            Assert.Equal(\"some_stuff,1[2, ]\", result);\n        }\n\n        [Fact]\n        public void TestParse__NoSpaceMeansNoOp()\n        {\n            Operator op = OperatorParser.Parse(\"some_stuff*\", out string result);\n\n            Assert.Equal(Operator.Assign, op);\n            Assert.Equal(\"some_stuff*\", result);\n        }\n\n        [Fact]\n        public void TestParse__SingleCharacterNotOp()\n        {\n            Operator op = OperatorParser.Parse(\"*\", out string result);\n\n            Assert.Equal(Operator.Assign, op);\n            Assert.Equal(\"*\", result);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/PassTest.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing Xunit;\nusing NSubstitute;\nusing ModuleManager;\nusing ModuleManager.Patches;\n\nnamespace ModuleManagerTests\n{\n    public class PassTest\n    {\n        [Fact]\n        public void TestConstructor__NameNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new Pass(null);\n            });\n\n            Assert.Equal(\"name\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__NameEmpty()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new Pass(\"\");\n            });\n\n            Assert.Contains(\"can't be empty\", ex.Message);\n            Assert.Equal(\"name\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestName()\n        {\n            Pass pass = new Pass(\":NOTINAMILLIONYEARS\");\n\n            Assert.Equal(\":NOTINAMILLIONYEARS\", pass.Name);\n        }\n\n        [Fact]\n        public void Test__Add__Enumerator()\n        {\n            IPatch[] patches =\n            {\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n            };\n\n            Pass pass = new Pass(\"blah\")\n            {\n                patches[0],\n                patches[1],\n                patches[2],\n            };\n\n            IPatch[] passPatches = pass.ToArray();\n            Assert.Equal(patches.Length, passPatches.Length);\n\n            for (int i = 0; i < patches.Length; i++)\n            {\n                Assert.Same(patches[i], passPatches[i]);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/PatchApplierTest.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing Xunit;\nusing NSubstitute;\nusing UnityEngine;\nusing ModuleManager;\nusing ModuleManager.Collections;\nusing ModuleManager.Logging;\nusing ModuleManager.Patches;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests\n{\n    public class PatchApplierTest\n    {\n        [Fact]\n        public void TestConstructor__ProgressNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new PatchApplier(null, Substitute.For<IBasicLogger>());\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__LoggerNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new PatchApplier(Substitute.For<IPatchProgress>(), null);\n            });\n\n            Assert.Equal(\"logger\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestApplyPatches__PatchesNull()\n        {\n            PatchApplier applier = new PatchApplier(Substitute.For<IPatchProgress>(), Substitute.For<IBasicLogger>());\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                applier.ApplyPatches(null);\n            });\n\n            Assert.Equal(\"patches\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestApplyPatches()\n        {\n            IBasicLogger logger = Substitute.For<IBasicLogger>();\n            IPatchProgress progress = Substitute.For<IPatchProgress>();\n            PatchApplier patchApplier = new PatchApplier(progress, logger);\n            IPass pass1 = Substitute.For<IPass>();\n            IPass pass2 = Substitute.For<IPass>();\n            IPass pass3 = Substitute.For<IPass>();\n            pass1.Name.Returns(\":PASS1\");\n            pass2.Name.Returns(\":PASS2\");\n            pass3.Name.Returns(\":PASS3\");\n\n            UrlDir.UrlConfig[] patchUrlConfigs = new UrlDir.UrlConfig[9];\n            IPatch[] patches = new IPatch[9];\n            for (int i = 0; i < patches.Length; i++)\n            {\n                patches[i] = Substitute.For<IPatch>();\n            }\n\n            patches[0].CountsAsPatch.Returns(false);\n            patches[1].CountsAsPatch.Returns(false);\n            patches[2].CountsAsPatch.Returns(false);\n            patches[3].CountsAsPatch.Returns(true);\n            patches[4].CountsAsPatch.Returns(true);\n            patches[5].CountsAsPatch.Returns(true);\n            patches[6].CountsAsPatch.Returns(true);\n            patches[7].CountsAsPatch.Returns(true);\n            patches[8].CountsAsPatch.Returns(true);\n\n            pass1.GetEnumerator().Returns(new ArrayEnumerator<IPatch>(patches[0], patches[1], patches[2]));\n            pass2.GetEnumerator().Returns(new ArrayEnumerator<IPatch>(patches[3], patches[4], patches[5]));\n            pass3.GetEnumerator().Returns(new ArrayEnumerator<IPatch>(patches[6], patches[7], patches[8]));\n\n            IPass[] patchList = new IPass[] { pass1, pass2, pass3 };\n\n            LinkedList<IProtoUrlConfig> databaseConfigs = Assert.IsType<LinkedList<IProtoUrlConfig>>(patchApplier.ApplyPatches(new[] { pass1, pass2, pass3 }));\n\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null, null);\n\n            logger.AssertNoWarning();\n            logger.AssertNoError();\n            logger.AssertNoException();\n\n            Received.InOrder(delegate\n            {\n                progress.PassStarted(pass1);\n                patches[0].Apply(databaseConfigs, progress, logger);\n                patches[1].Apply(databaseConfigs, progress, logger);\n                patches[2].Apply(databaseConfigs, progress, logger);\n                progress.PassStarted(pass2);\n                patches[3].Apply(databaseConfigs, progress, logger);\n                progress.PatchApplied();\n                patches[4].Apply(databaseConfigs, progress, logger);\n                progress.PatchApplied();\n                patches[5].Apply(databaseConfigs, progress, logger);\n                progress.PatchApplied();\n                progress.PassStarted(pass3);\n                patches[6].Apply(databaseConfigs, progress, logger);\n                progress.PatchApplied();\n                patches[7].Apply(databaseConfigs, progress, logger);\n                progress.PatchApplied();\n                patches[8].Apply(databaseConfigs, progress, logger);\n                progress.PatchApplied();\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/PatchExtractorTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Logging;\nusing ModuleManager.Patches;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\nusing ModuleManager.Tags;\n\nnamespace ModuleManagerTests\n{\n    public class PatchExtractorTest\n    {\n        private readonly UrlDir root;\n        private readonly UrlDir.UrlFile file;\n\n        private readonly IPatchProgress progress;\n        private readonly IBasicLogger logger;\n        private readonly INeedsChecker needsChecker;\n        private readonly ITagListParser tagListParser;\n        private readonly IProtoPatchBuilder protoPatchBuilder;\n        private readonly IPatchCompiler patchCompiler;\n        private readonly PatchExtractor patchExtractor;\n\n        public PatchExtractorTest()\n        {\n            root = UrlBuilder.CreateRoot();\n            file = UrlBuilder.CreateFile(\"abc/def.cfg\", root);\n            \n            progress = Substitute.For<IPatchProgress>();\n            logger = Substitute.For<IBasicLogger>();\n            needsChecker = Substitute.For<INeedsChecker>();\n            tagListParser = Substitute.For<ITagListParser>();\n            protoPatchBuilder = Substitute.For<IProtoPatchBuilder>();\n            patchCompiler = Substitute.For<IPatchCompiler>();\n            patchExtractor = new PatchExtractor(progress, logger, needsChecker, tagListParser, protoPatchBuilder, patchCompiler);\n        }\n\n        [Fact]\n        public void TestConstructor__ProgressNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new PatchExtractor(null, logger, needsChecker, tagListParser, protoPatchBuilder, patchCompiler);\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__LoggerNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new PatchExtractor(progress, null, needsChecker, tagListParser, protoPatchBuilder, patchCompiler);\n            });\n\n            Assert.Equal(\"logger\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__NeedsCheckerNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new PatchExtractor(progress, logger, null, tagListParser, protoPatchBuilder, patchCompiler);\n            });\n\n            Assert.Equal(\"needsChecker\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__TagListParserNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new PatchExtractor(progress, logger, needsChecker, null, protoPatchBuilder, patchCompiler);\n            });\n\n            Assert.Equal(\"tagListParser\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__ProtoPatchBuilderNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new PatchExtractor(progress, logger, needsChecker, tagListParser, null, patchCompiler);\n            });\n\n            Assert.Equal(\"protoPatchBuilder\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__PatchCompilerNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new PatchExtractor(progress, logger, needsChecker, tagListParser, protoPatchBuilder, null);\n            });\n\n            Assert.Equal(\"patchCompiler\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestExtractPatch__ProtoPatchNull()\n        {\n            UrlDir.UrlConfig patchConfig = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"NODE\"), root);\n\n            protoPatchBuilder.Build(patchConfig, Command.Insert, Arg.Any<ITagList>()).Returns(null, new ProtoPatch[0]);\n\n            Assert.Null(patchExtractor.ExtractPatch(patchConfig));\n\n            needsChecker.DidNotReceiveWithAnyArgs().CheckNeedsExpression(null);\n\n            AssertNoErrors();\n\n            progress.DidNotReceive().PatchAdded();\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null);\n        }\n\n        [Fact]\n        public void TestExtractPatch()\n        {\n            UrlDir.UrlConfig urlConfig = CreateConfig(\"@NODE_TYPE\");\n\n            ITagList tagList = Substitute.For<ITagList>();\n            tagListParser.Parse(\"NODE_TYPE\", urlConfig).Returns(tagList);\n\n            IPassSpecifier passSpecifier = Substitute.For<IPassSpecifier>();\n            ProtoPatch protoPatch = new ProtoPatch(\n                urlConfig,\n                Command.Edit,\n                \"NODE_TYPE\",\n                \"nodeName\",\n                null,\n                \"has\",\n                passSpecifier\n            );\n\n            protoPatchBuilder.Build(urlConfig, Command.Edit, tagList).Returns(protoPatch);\n            passSpecifier.CheckNeeds(needsChecker, progress).Returns(true);\n\n            IPatch patch = Substitute.For<IPatch>();\n            patchCompiler.CompilePatch(protoPatch).Returns(patch);\n\n            Assert.Same(patch, patchExtractor.ExtractPatch(urlConfig));\n\n            AssertNoErrors();\n\n            needsChecker.Received().CheckNeedsRecursive(urlConfig.config, urlConfig);\n            needsChecker.DidNotReceiveWithAnyArgs().CheckNeedsExpression(null);\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null);\n        }\n\n        [Fact]\n        public void TestExtractPatch__Needs()\n        {\n            UrlDir.UrlConfig urlConfig = CreateConfig(\"@NODE_TYPE\");\n\n            ITagList tagList = Substitute.For<ITagList>();\n            tagListParser.Parse(\"NODE_TYPE\", urlConfig).Returns(tagList);\n\n            IPassSpecifier passSpecifier = Substitute.For<IPassSpecifier>();\n            ProtoPatch protoPatch = new ProtoPatch(\n                urlConfig,\n                Command.Edit,\n                \"NODE_TYPE\",\n                \"nodeName\",\n                \"needs\",\n                \"has\",\n                passSpecifier\n            );\n\n            protoPatchBuilder.Build(urlConfig, Command.Edit, tagList).Returns(protoPatch);\n            needsChecker.CheckNeedsExpression(\"needs\").Returns(true);\n            passSpecifier.CheckNeeds(needsChecker, progress).Returns(true);\n\n            IPatch patch = Substitute.For<IPatch>();\n            patchCompiler.CompilePatch(protoPatch).Returns(patch);\n\n            Assert.Same(patch, patchExtractor.ExtractPatch(urlConfig));\n\n            AssertNoErrors();\n\n            needsChecker.Received().CheckNeedsRecursive(urlConfig.config, urlConfig);\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null);\n        }\n\n        [Fact]\n        public void TestExtractPatch__NeedsUnsatisfied()\n        {\n            UrlDir.UrlConfig urlConfig = CreateConfig(\"@NODE_TYPE\");\n\n            ITagList tagList = Substitute.For<ITagList>();\n            tagListParser.Parse(\"NODE_TYPE\", urlConfig).Returns(tagList);\n\n            IPassSpecifier passSpecifier = Substitute.For<IPassSpecifier>();\n            ProtoPatch protoPatch = new ProtoPatch(\n                urlConfig,\n                Command.Edit,\n                \"NODE_TYPE\",\n                \"nodeName\",\n                \"needs\",\n                \"has\",\n                passSpecifier\n            );\n\n            protoPatchBuilder.Build(urlConfig, Command.Edit, tagList).Returns(protoPatch);\n            needsChecker.CheckNeedsExpression(\"needs\").Returns(false);\n\n            Assert.Null(patchExtractor.ExtractPatch(urlConfig));\n\n            AssertNoErrors();\n\n            passSpecifier.DidNotReceiveWithAnyArgs().CheckNeeds(null, null);\n            needsChecker.DidNotReceiveWithAnyArgs().CheckNeedsRecursive(null, null);\n            patchCompiler.DidNotReceiveWithAnyArgs().CompilePatch(null);\n\n            progress.Received().NeedsUnsatisfiedRoot(urlConfig);\n        }\n\n        [Fact]\n        public void TestExtractPatch__NeedsUnsatisfiedPassSpecifier()\n        {\n            UrlDir.UrlConfig urlConfig = CreateConfig(\"@NODE_TYPE\");\n\n            ITagList tagList = Substitute.For<ITagList>();\n            tagListParser.Parse(\"NODE_TYPE\", urlConfig).Returns(tagList);\n\n            IPassSpecifier passSpecifier = Substitute.For<IPassSpecifier>();\n            ProtoPatch protoPatch = new ProtoPatch(\n                urlConfig,\n                Command.Edit,\n                \"NODE_TYPE\",\n                \"nodeName\",\n                \"needs\",\n                \"has\",\n                passSpecifier\n            );\n\n            protoPatchBuilder.Build(urlConfig, Command.Edit, tagList).Returns(protoPatch);\n            needsChecker.CheckNeedsExpression(\"needs\").Returns(true);\n            passSpecifier.CheckNeeds(needsChecker, progress).Returns(false);\n\n            Assert.Null(patchExtractor.ExtractPatch(urlConfig));\n\n            AssertNoErrors();\n\n            needsChecker.DidNotReceiveWithAnyArgs().CheckNeedsRecursive(null, null);\n            patchCompiler.DidNotReceiveWithAnyArgs().CompilePatch(null);\n\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null);\n        }\n\n        [Fact]\n        public void TestExtractPatch__Null()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                patchExtractor.ExtractPatch(null);\n            });\n\n            Assert.Equal(\"urlConfig\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestExtractPatch__NotBracketBalanced()\n        {\n            UrlDir.UrlConfig config1 = CreateConfig(\"@NODE:FOR[\");\n            UrlDir.UrlConfig config2 = CreateConfig(\"NODE:HAS[#foo[]\");\n\n            patchExtractor.ExtractPatch(config1);\n            patchExtractor.ExtractPatch(config2);\n        \n            progress.DidNotReceiveWithAnyArgs().Exception(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null, null);\n        \n            Received.InOrder(delegate\n            {\n                progress.Received().Error(config1, \"Error - node name does not have balanced brackets (or a space - if so replace with ?):\\nabc/def/@NODE:FOR[\");\n                progress.Received().Error(config2, \"Error - node name does not have balanced brackets (or a space - if so replace with ?):\\nabc/def/NODE:HAS[#foo[]\");\n            });\n\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null);\n        }\n\n        [Fact]\n        public void TestExtractPatch__InvalidCommand__Replace()\n        {\n            UrlDir.UrlConfig urlConfig = CreateConfig(\"%NODE\");\n            Assert.Null(patchExtractor.ExtractPatch(urlConfig));\n\n            progress.Received().Error(urlConfig, \"Error - replace command (%) is not valid on a root node: abc/def/%NODE\");\n        }\n\n        [Fact]\n        public void TestExtractPatch__InvalidCommand__Create()\n        {\n            UrlDir.UrlConfig urlConfig = CreateConfig(\"&NODE\");\n            Assert.Null(patchExtractor.ExtractPatch(urlConfig));\n\n            progress.Received().Error(urlConfig, \"Error - create command (&) is not valid on a root node: abc/def/&NODE\");\n        }\n\n        [Fact]\n        public void TestExtractPatch__InvalidCommand__Rename()\n        {\n            UrlDir.UrlConfig urlConfig = CreateConfig(\"|NODE\");\n            Assert.Null(patchExtractor.ExtractPatch(urlConfig));\n\n            progress.Received().Error(urlConfig, \"Error - rename command (|) is not valid on a root node: abc/def/|NODE\");\n        }\n\n        [Fact]\n        public void TestExtractPatch__InvalidCommand__Paste()\n        {\n            UrlDir.UrlConfig urlConfig = CreateConfig(\"#NODE\");\n            Assert.Null(patchExtractor.ExtractPatch(urlConfig));\n\n            progress.Received().Error(urlConfig, \"Error - paste command (#) is not valid on a root node: abc/def/#NODE\");\n        }\n\n        [Fact]\n        public void TestExtractPatch__InvalidCommand__Special()\n        {\n            UrlDir.UrlConfig urlConfig = CreateConfig(\"*NODE\");\n            Assert.Null(patchExtractor.ExtractPatch(urlConfig));\n\n            progress.Received().Error(urlConfig, \"Error - special command (*) is not valid on a root node: abc/def/*NODE\");\n        }\n\n        [Fact]\n        public void TestExtractPatch__TagListBadlyFormatted()\n        {\n            UrlDir.UrlConfig urlConfig = CreateConfig(\"badSomehow\");\n            tagListParser.When(t => t.Parse(\"badSomehow\", urlConfig)).Throw(new FormatException(\"badly formatted\"));\n            Assert.Null(patchExtractor.ExtractPatch(urlConfig));\n\n            progress.Received().Error(urlConfig, \"Cannot parse node name as tag list: badly formatted\\non: abc/def/badSomehow\");\n        }\n\n        [Fact]\n        public void TestExtractPatch__ProtoPatchFailed()\n        {\n            UrlDir.UrlConfig urlConfig = CreateConfig(\"NODE\");\n            protoPatchBuilder.Build(urlConfig, Command.Insert, Arg.Any<ITagList>()).Returns((ProtoPatch)null);\n            Assert.Null(patchExtractor.ExtractPatch(urlConfig));\n\n            AssertNoErrors();\n        }\n\n        [Fact]\n        public void TestExtractPatch__Exception()\n        {\n            UrlDir.UrlConfig urlConfig = CreateConfig(\"NODE\");\n            Exception ex = new Exception();\n            tagListParser.When(t => t.Parse(\"NODE\", urlConfig)).Throw(ex);\n            Assert.Null(patchExtractor.ExtractPatch(urlConfig));\n\n            progress.Received().Exception(urlConfig, \"Exception while attempting to create patch from config: abc/def/NODE\", ex);\n        }\n\n        private UrlDir.UrlConfig CreateConfig(string name)\n        {\n            ConfigNode node = new TestConfigNode(name)\n            {\n                { \"name\", \"snack\" },\n                { \"cheese\", \"gouda\" },\n                { \"bread\", \"sourdough\" },\n                new ConfigNode(\"wine\"),\n                new ConfigNode(\"fruit\"),\n            };\n        \n            node.id = \"hungry?\";\n        \n            return UrlBuilder.CreateConfig(node, file);\n        }\n\n        private void AssertNoErrors()\n        {\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null, null);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/PatchListTest.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Xunit;\nusing NSubstitute;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Patches;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests\n{\n    public class PatchListTest\n    {\n        [Fact]\n        public void TestConstructor__ModListNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new PatchList(null, new IPatch[0], Substitute.For<IPatchProgress>());\n            });\n\n            Assert.Equal(\"modList\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__PatchesNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new PatchList(new string[0], null, Substitute.For<IPatchProgress>());\n            });\n\n            Assert.Equal(\"patches\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__ProgressNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new PatchList(new string[0], new IPatch[0], null);\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__UnknownMod()\n        {\n            IPatch patch = Substitute.For<IPatch>();\n            UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"NODE\"));\n            patch.PassSpecifier.Returns(new BeforePassSpecifier(\"mod3\", urlConfig));\n            IPatchProgress progress = Substitute.For<IPatchProgress>();\n\n            KeyNotFoundException ex = Assert.Throws<KeyNotFoundException>(delegate\n            {\n                new PatchList(new[] { \"mod1\", \"mod2\" }, new[] { patch }, progress);\n            });\n\n            Assert.Equal(\"Mod 'mod3' not found\", ex.Message);\n\n            progress.DidNotReceive().PatchAdded();\n        }\n\n        [Fact]\n        public void TestConstructor__UnknownPassSpecifier()\n        {\n            IPatch patch = Substitute.For<IPatch>();\n            UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"NODE\"));\n            IPassSpecifier passSpecifier = Substitute.For<IPassSpecifier>();\n            passSpecifier.Descriptor.Returns(\":SOMEPASS\");\n            patch.PassSpecifier.Returns(passSpecifier);\n            IPatchProgress progress = Substitute.For<IPatchProgress>();\n\n            NotImplementedException ex = Assert.Throws<NotImplementedException>(delegate\n            {\n                new PatchList(new string[0], new[] { patch }, progress);\n            });\n\n            Assert.Equal(\"Don't know what to do with pass specifier: :SOMEPASS\", ex.Message);\n\n            progress.DidNotReceive().PatchAdded();\n        }\n\n        [Fact]\n        public void Test__Lifecycle()\n        {\n            IPatch[] patches = new IPatch[]\n            {\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n                Substitute.For<IPatch>(),\n            };\n\n            UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"NODE\"));\n\n            patches[00].PassSpecifier.Returns(new InsertPassSpecifier());\n            patches[01].PassSpecifier.Returns(new InsertPassSpecifier());\n            patches[02].PassSpecifier.Returns(new FirstPassSpecifier());\n            patches[03].PassSpecifier.Returns(new FirstPassSpecifier());\n            patches[04].PassSpecifier.Returns(new LegacyPassSpecifier());\n            patches[05].PassSpecifier.Returns(new LegacyPassSpecifier());\n            patches[06].PassSpecifier.Returns(new BeforePassSpecifier(\"mod1\", urlConfig));\n            patches[07].PassSpecifier.Returns(new BeforePassSpecifier(\"MOD1\", urlConfig));\n            patches[08].PassSpecifier.Returns(new ForPassSpecifier(\"mod1\", urlConfig));\n            patches[09].PassSpecifier.Returns(new ForPassSpecifier(\"MOD1\", urlConfig));\n            patches[10].PassSpecifier.Returns(new AfterPassSpecifier(\"mod1\", urlConfig));\n            patches[11].PassSpecifier.Returns(new AfterPassSpecifier(\"MOD1\", urlConfig));\n            patches[12].PassSpecifier.Returns(new LastPassSpecifier(\"mod1\"));\n            patches[13].PassSpecifier.Returns(new LastPassSpecifier(\"MOD1\"));\n            patches[14].PassSpecifier.Returns(new BeforePassSpecifier(\"mod2\", urlConfig));\n            patches[15].PassSpecifier.Returns(new BeforePassSpecifier(\"MOD2\", urlConfig));\n            patches[16].PassSpecifier.Returns(new ForPassSpecifier(\"mod2\", urlConfig));\n            patches[17].PassSpecifier.Returns(new ForPassSpecifier(\"MOD2\", urlConfig));\n            patches[18].PassSpecifier.Returns(new AfterPassSpecifier(\"mod2\", urlConfig));\n            patches[19].PassSpecifier.Returns(new AfterPassSpecifier(\"MOD2\", urlConfig));\n            patches[20].PassSpecifier.Returns(new LastPassSpecifier(\"mod2\"));\n            patches[21].PassSpecifier.Returns(new LastPassSpecifier(\"MOD2\"));\n            patches[22].PassSpecifier.Returns(new LastPassSpecifier(\"mod3\"));\n            patches[23].PassSpecifier.Returns(new FinalPassSpecifier());\n            patches[24].PassSpecifier.Returns(new FinalPassSpecifier());\n\n            patches[00].CountsAsPatch.Returns(false);\n            patches[01].CountsAsPatch.Returns(false);\n            patches[02].CountsAsPatch.Returns(true);\n            patches[03].CountsAsPatch.Returns(true);\n            patches[04].CountsAsPatch.Returns(true);\n            patches[05].CountsAsPatch.Returns(true);\n            patches[06].CountsAsPatch.Returns(true);\n            patches[07].CountsAsPatch.Returns(true);\n            patches[08].CountsAsPatch.Returns(true);\n            patches[09].CountsAsPatch.Returns(true);\n            patches[10].CountsAsPatch.Returns(true);\n            patches[11].CountsAsPatch.Returns(true);\n            patches[12].CountsAsPatch.Returns(true);\n            patches[13].CountsAsPatch.Returns(true);\n            patches[14].CountsAsPatch.Returns(true);\n            patches[15].CountsAsPatch.Returns(true);\n            patches[16].CountsAsPatch.Returns(true);\n            patches[17].CountsAsPatch.Returns(true);\n            patches[18].CountsAsPatch.Returns(true);\n            patches[19].CountsAsPatch.Returns(true);\n            patches[20].CountsAsPatch.Returns(true);\n            patches[21].CountsAsPatch.Returns(true);\n            patches[22].CountsAsPatch.Returns(true);\n            patches[23].CountsAsPatch.Returns(true);\n            patches[24].CountsAsPatch.Returns(true);\n\n            IPatchProgress progress = Substitute.For<IPatchProgress>();\n\n            PatchList patchList = new PatchList(new[] { \"mod1\", \"mod2\" }, patches, progress);\n\n            IPass[] passes = patchList.ToArray();\n\n            Assert.Equal(13, passes.Length);\n\n            Assert.Equal(\":INSERT (initial)\", passes[0].Name);\n            Assert.Equal(new[] { patches[0], patches[1] }, passes[0]);\n\n            Assert.Equal(\":FIRST\", passes[1].Name);\n            Assert.Equal(new[] { patches[2], patches[3] }, passes[1]);\n\n            Assert.Equal(\":LEGACY (default)\", passes[2].Name);\n            Assert.Equal(new[] { patches[4], patches[5] }, passes[2]);\n\n            Assert.Equal(\":BEFORE[MOD1]\", passes[3].Name);\n            Assert.Equal(new[] { patches[6], patches[7] }, passes[3]);\n\n            Assert.Equal(\":FOR[MOD1]\", passes[4].Name);\n            Assert.Equal(new[] { patches[8], patches[9] }, passes[4]);\n\n            Assert.Equal(\":AFTER[MOD1]\", passes[5].Name);\n            Assert.Equal(new[] { patches[10], patches[11] }, passes[5]);\n\n            Assert.Equal(\":BEFORE[MOD2]\", passes[6].Name);\n            Assert.Equal(new[] { patches[14], patches[15] }, passes[6]);\n\n            Assert.Equal(\":FOR[MOD2]\", passes[7].Name);\n            Assert.Equal(new[] { patches[16], patches[17] }, passes[7]);\n\n            Assert.Equal(\":AFTER[MOD2]\", passes[8].Name);\n            Assert.Equal(new[] { patches[18], patches[19] }, passes[8]);\n\n            Assert.Equal(\":LAST[MOD1]\", passes[9].Name);\n            Assert.Equal(new[] { patches[12], patches[13] }, passes[9]);\n\n            Assert.Equal(\":LAST[MOD2]\", passes[10].Name);\n            Assert.Equal(new[] { patches[20], patches[21] }, passes[10]);\n\n            Assert.Equal(\":LAST[MOD3]\", passes[11].Name);\n            Assert.Equal(new[] { patches[22] }, passes[11]);\n\n            Assert.Equal(\":FINAL\", passes[12].Name);\n            Assert.Equal(new[] { patches[23], patches[24] }, passes[12]);\n\n            progress.Received(23).PatchAdded();\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Patches/CopyPatchTest.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Xunit;\nusing NSubstitute;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Logging;\nusing ModuleManager.Patches;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests.Patches\n{\n    public class CopyPatchTest\n    {\n        [Fact]\n        public void TestConstructor__urlConfigNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new CopyPatch(null, Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            });\n\n            Assert.Equal(\"urlConfig\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__nodeMatcherNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new CopyPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), null, Substitute.For<IPassSpecifier>());\n            });\n\n            Assert.Equal(\"nodeMatcher\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__passSpecifierNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new CopyPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), null);\n            });\n\n            Assert.Equal(\"passSpecifier\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestUrlConfig()\n        {\n            UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode());\n            CopyPatch patch = new CopyPatch(urlConfig, Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n\n            Assert.Same(urlConfig, patch.UrlConfig);\n        }\n\n        [Fact]\n        public void TestNodeMatcher()\n        {\n            INodeMatcher nodeMatcher = Substitute.For<INodeMatcher>();\n            CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), nodeMatcher, Substitute.For<IPassSpecifier>());\n\n            Assert.Same(nodeMatcher, patch.NodeMatcher);\n        }\n\n        [Fact]\n        public void TestPassSpecifier()\n        {\n            IPassSpecifier passSpecifier = Substitute.For<IPassSpecifier>();\n            CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), passSpecifier);\n\n            Assert.Same(passSpecifier, patch.PassSpecifier);\n        }\n\n        [Fact]\n        public void TestCountsAsPatch()\n        {\n            CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            Assert.True(patch.CountsAsPatch);\n        }\n\n        [Fact]\n        public void TestApply()\n        {\n            UrlDir.UrlFile file = UrlBuilder.CreateFile(\"abc/def.cfg\");\n\n            ConfigNode config1 = new TestConfigNode(\"NODE\")\n            {\n                { \"foo\", \"bar\" },\n            };\n\n            ConfigNode config2 = new TestConfigNode(\"NODE\")\n            {\n                { \"foo\", \"bar\" },\n            };\n\n            ConfigNode config3 = new ConfigNode(\"NODE\");\n            ConfigNode config4 = new ConfigNode(\"NODE\");\n\n            INodeMatcher nodeMatcher = Substitute.For<INodeMatcher>();\n\n            nodeMatcher.IsMatch(config1).Returns(false);\n            nodeMatcher.IsMatch(config2).Returns(true);\n            nodeMatcher.IsMatch(config3).Returns(false);\n            nodeMatcher.IsMatch(config4).Returns(true);\n\n            CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig(\"ghi/jkl\", new TestConfigNode(\"@NODE\")\n            {\n                { \"@foo\", \"baz\" },\n                { \"pqr\", \"stw\" },\n            }), nodeMatcher, Substitute.For<IPassSpecifier>());\n\n            IProtoUrlConfig protoUrlConfig1 = Substitute.For<IProtoUrlConfig>();\n            IProtoUrlConfig protoUrlConfig2 = Substitute.For<IProtoUrlConfig>();\n            IProtoUrlConfig protoUrlConfig3 = Substitute.For<IProtoUrlConfig>();\n            IProtoUrlConfig protoUrlConfig4 = Substitute.For<IProtoUrlConfig>();\n\n            protoUrlConfig1.Node.Returns(config1);\n            protoUrlConfig2.Node.Returns(config2);\n            protoUrlConfig3.Node.Returns(config3);\n            protoUrlConfig4.Node.Returns(config4);\n\n            protoUrlConfig1.UrlFile.Returns(file);\n            protoUrlConfig2.UrlFile.Returns(file);\n            protoUrlConfig3.UrlFile.Returns(file);\n            protoUrlConfig4.UrlFile.Returns(file);\n\n            LinkedList<IProtoUrlConfig> configs = new LinkedList<IProtoUrlConfig>();\n            configs.AddLast(protoUrlConfig1);\n            configs.AddLast(protoUrlConfig2);\n            configs.AddLast(protoUrlConfig3);\n            configs.AddLast(protoUrlConfig4);\n\n            IPatchProgress progress = Substitute.For<IPatchProgress>();\n            IBasicLogger logger = Substitute.For<IBasicLogger>();\n\n            patch.Apply(configs, progress, logger);\n\n            IProtoUrlConfig[] newConfigs = configs.ToArray();\n\n            Assert.Equal(6, newConfigs.Length);\n\n            Assert.Same(protoUrlConfig1, newConfigs[0]);\n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"foo\", \"bar\" },\n            }, newConfigs[0].Node);\n\n            Assert.Same(protoUrlConfig2, newConfigs[1]);\n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"foo\", \"bar\" },\n            }, newConfigs[1].Node);\n\n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"foo\", \"baz\" },\n                { \"pqr\", \"stw\" },\n            }, newConfigs[2].Node);\n            Assert.Same(file, newConfigs[2].UrlFile);\n\n            Assert.Same(protoUrlConfig3, newConfigs[3]);\n            AssertNodesEqual(new ConfigNode(\"NODE\"), newConfigs[3].Node);\n\n            Assert.Same(protoUrlConfig4, newConfigs[4]);\n            AssertNodesEqual(new ConfigNode(\"NODE\"), newConfigs[4].Node);\n            \n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"pqr\", \"stw\" },\n            }, newConfigs[5].Node);\n            Assert.Same(file, newConfigs[5].UrlFile);\n\n            Received.InOrder(delegate\n            {\n                progress.ApplyingCopy(protoUrlConfig2, patch.UrlConfig);\n                progress.ApplyingCopy(protoUrlConfig4, patch.UrlConfig);\n            });\n\n            progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null);\n            progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null);\n\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null, null);\n        }\n\n        [Fact]\n        public void TestApply__NameChanged()\n        {\n            UrlDir.UrlFile file = UrlBuilder.CreateFile(\"abc/def.cfg\");\n\n            ConfigNode config = new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"000\" },\n                { \"foo\", \"bar\" },\n            };\n\n            INodeMatcher nodeMatcher = Substitute.For<INodeMatcher>();\n\n            nodeMatcher.IsMatch(config).Returns(true);\n\n            CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig(\"ghi/jkl\", new TestConfigNode(\"@NODE\")\n            {\n                { \"@name\", \"001\" },\n                { \"@foo\", \"baz\" },\n                { \"pqr\", \"stw\" },\n            }), nodeMatcher, Substitute.For<IPassSpecifier>());\n\n            IProtoUrlConfig protoConfig = Substitute.For<IProtoUrlConfig>();\n            protoConfig.Node.Returns(config);\n            protoConfig.UrlFile.Returns(file);\n\n            LinkedList<IProtoUrlConfig> configs = new LinkedList<IProtoUrlConfig>();\n            configs.AddLast(protoConfig);\n\n            IPatchProgress progress = Substitute.For<IPatchProgress>();\n            IBasicLogger logger = Substitute.For<IBasicLogger>();\n\n            patch.Apply(configs, progress, logger);\n\n            IProtoUrlConfig[] newConfigs = configs.ToArray();\n\n            Assert.Equal(2, newConfigs.Length);\n\n            Assert.Same(protoConfig, newConfigs[0]);\n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"000\" },\n                { \"foo\", \"bar\" },\n            }, newConfigs[0].Node);\n            \n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"001\" },\n                { \"foo\", \"baz\" },\n                { \"pqr\", \"stw\" },\n            }, newConfigs[1].Node);\n            Assert.Same(file, newConfigs[1].UrlFile);\n            \n            progress.Received().ApplyingCopy(protoConfig, patch.UrlConfig);\n\n            progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null);\n            progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null);\n\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null, null);\n        }\n\n        [Fact]\n        public void TestApply__NameNotChanged()\n        {\n            ConfigNode config = new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"000\" },\n                { \"foo\", \"bar\" },\n            };\n\n            INodeMatcher nodeMatcher = Substitute.For<INodeMatcher>();\n\n            nodeMatcher.IsMatch(config).Returns(true);\n\n            CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig(\"ghi/jkl\", new TestConfigNode(\"+NODE\")\n            {\n                { \"@foo\", \"baz\" },\n                { \"pqr\", \"stw\" },\n            }), nodeMatcher, Substitute.For<IPassSpecifier>());\n\n            IProtoUrlConfig protoConfig = Substitute.For<IProtoUrlConfig>();\n            protoConfig.Node.Returns(config);\n            protoConfig.FullUrl.Returns(\"abc/def.cfg/NODE\");\n\n            LinkedList<IProtoUrlConfig> configs = new LinkedList<IProtoUrlConfig>();\n            configs.AddLast(protoConfig);\n\n            IPatchProgress progress = Substitute.For<IPatchProgress>();\n            IBasicLogger logger = Substitute.For<IBasicLogger>();\n\n            patch.Apply(configs, progress, logger);\n\n            Assert.Single(configs);\n\n            Assert.Same(protoConfig, configs.First.Value);\n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"000\" },\n                { \"foo\", \"bar\" },\n            }, configs.First.Value.Node);\n\n            progress.Received().Error(patch.UrlConfig, \"Error - when applying copy ghi/jkl/+NODE to abc/def.cfg/NODE - the copy needs to have a different name than the parent (use @name = xxx)\");\n\n            progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null);\n            progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null);\n            progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null);\n            \n            progress.DidNotReceiveWithAnyArgs().Exception(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null, null);\n        }\n\n        [Fact]\n        public void TestApply__DatabaseConfigsNullNull()\n        {\n            CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                patch.Apply(null, Substitute.For<IPatchProgress>(), Substitute.For<IBasicLogger>());\n            });\n\n            Assert.Equal(\"databaseConfigs\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestApply__ProgressNull()\n        {\n            CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                patch.Apply(new LinkedList<IProtoUrlConfig>(), null, Substitute.For<IBasicLogger>());\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestApply__LoggerNull()\n        {\n            CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                patch.Apply(new LinkedList<IProtoUrlConfig>(), Substitute.For<IPatchProgress>(), null);\n            });\n\n            Assert.Equal(\"logger\", ex.ParamName);\n        }\n\n        private void AssertNodesEqual(ConfigNode expected, ConfigNode actual)\n        {\n            Assert.Equal(expected.ToString(), actual.ToString());\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Patches/DeletePatchTest.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing Xunit;\nusing NSubstitute;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Logging;\nusing ModuleManager.Patches;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests.Patches\n{\n    public class DeletePatchTest\n    {\n        [Fact]\n        public void TestConstructor__urlConfigNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new DeletePatch(null, Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            });\n\n            Assert.Equal(\"urlConfig\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__nodeMatcherNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new DeletePatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), null, Substitute.For<IPassSpecifier>());\n            });\n\n            Assert.Equal(\"nodeMatcher\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__passSpecifierNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new DeletePatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), null);\n            });\n\n            Assert.Equal(\"passSpecifier\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestUrlConfig()\n        {\n            UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode());\n            DeletePatch patch = new DeletePatch(urlConfig, Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n\n            Assert.Same(urlConfig, patch.UrlConfig);\n        }\n\n        [Fact]\n        public void TestNodeMatcher()\n        {\n            INodeMatcher nodeMatcher = Substitute.For<INodeMatcher>();\n            DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), nodeMatcher, Substitute.For<IPassSpecifier>());\n\n            Assert.Same(nodeMatcher, patch.NodeMatcher);\n        }\n\n        [Fact]\n        public void TestPassSpecifier()\n        {\n            IPassSpecifier passSpecifier = Substitute.For<IPassSpecifier>();\n            DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), passSpecifier);\n\n            Assert.Same(passSpecifier, patch.PassSpecifier);\n        }\n\n        [Fact]\n        public void TestCountsAsPatch()\n        {\n            DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            Assert.True(patch.CountsAsPatch);\n        }\n\n        [Fact]\n        public void TestApply()\n        {\n            ConfigNode config1 = new ConfigNode(\"NODE\");\n            ConfigNode config2 = new ConfigNode(\"NODE\");\n            ConfigNode config3 = new ConfigNode(\"NODE\");\n            ConfigNode config4 = new ConfigNode(\"NODE\");\n\n            INodeMatcher nodeMatcher = Substitute.For<INodeMatcher>();\n\n            nodeMatcher.IsMatch(config1).Returns(false);\n            nodeMatcher.IsMatch(config2).Returns(true);\n            nodeMatcher.IsMatch(config3).Returns(false);\n            nodeMatcher.IsMatch(config4).Returns(true);\n\n            DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode(\"!NODE\")), nodeMatcher, Substitute.For<IPassSpecifier>());\n\n            IProtoUrlConfig urlConfig1 = Substitute.For<IProtoUrlConfig>();\n            IProtoUrlConfig urlConfig2 = Substitute.For<IProtoUrlConfig>();\n            IProtoUrlConfig urlConfig3 = Substitute.For<IProtoUrlConfig>();\n            IProtoUrlConfig urlConfig4 = Substitute.For<IProtoUrlConfig>();\n\n            urlConfig1.Node.Returns(config1);\n            urlConfig2.Node.Returns(config2);\n            urlConfig3.Node.Returns(config3);\n            urlConfig4.Node.Returns(config4);\n\n            LinkedList<IProtoUrlConfig> configs = new LinkedList<IProtoUrlConfig>();\n            configs.AddLast(urlConfig1);\n            configs.AddLast(urlConfig2);\n            configs.AddLast(urlConfig3);\n            configs.AddLast(urlConfig4);\n\n            IPatchProgress progress = Substitute.For<IPatchProgress>();\n            IBasicLogger logger = Substitute.For<IBasicLogger>();\n\n            patch.Apply(configs, progress, logger);\n\n            Assert.Equal(new[] { urlConfig1, urlConfig3 }, configs);\n\n            Received.InOrder(delegate\n            {\n                progress.ApplyingDelete(urlConfig2, patch.UrlConfig);\n                progress.ApplyingDelete(urlConfig4, patch.UrlConfig);\n            });\n\n            progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null);\n            progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null);\n\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null, null);\n        }\n\n        [Fact]\n        public void TestApply__DatabaseConfigsNull()\n        {\n            DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                patch.Apply(null, Substitute.For<IPatchProgress>(), Substitute.For<IBasicLogger>());\n            });\n\n            Assert.Equal(\"databaseConfigs\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestApply__ProgressNull()\n        {\n            DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                patch.Apply(new LinkedList<IProtoUrlConfig>(), null, Substitute.For<IBasicLogger>());\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestApply__LoggerNull()\n        {\n            DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                patch.Apply(new LinkedList<IProtoUrlConfig>(), Substitute.For<IPatchProgress>(), null);\n            });\n\n            Assert.Equal(\"logger\", ex.ParamName);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Patches/EditPatchTest.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Xunit;\nusing NSubstitute;\nusing UnityEngine;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Logging;\nusing ModuleManager.Patches;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests.Patches\n{\n    public class EditPatchTest\n    {\n        [Fact]\n        public void TestConstructor__urlConfigNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new EditPatch(null, Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            });\n\n            Assert.Equal(\"urlConfig\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__nodeMatcherNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new EditPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), null, Substitute.For<IPassSpecifier>());\n            });\n\n            Assert.Equal(\"nodeMatcher\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__passSpecifierNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new EditPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), null);\n            });\n\n            Assert.Equal(\"passSpecifier\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestUrlConfig()\n        {\n            UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode());\n            EditPatch patch = new EditPatch(urlConfig, Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n\n            Assert.Same(urlConfig, patch.UrlConfig);\n        }\n\n        [Fact]\n        public void TestNodeMatcher()\n        {\n            INodeMatcher nodeMatcher = Substitute.For<INodeMatcher>();\n            EditPatch patch = new EditPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), nodeMatcher, Substitute.For<IPassSpecifier>());\n\n            Assert.Same(nodeMatcher, patch.NodeMatcher);\n        }\n\n        [Fact]\n        public void TestPassSpecifier()\n        {\n            IPassSpecifier passSpecifier = Substitute.For<IPassSpecifier>();\n            EditPatch patch = new EditPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), passSpecifier);\n\n            Assert.Same(passSpecifier, patch.PassSpecifier);\n        }\n\n        [Fact]\n        public void TestCountsAsPatch()\n        {\n            EditPatch patch = new EditPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            Assert.True(patch.CountsAsPatch);\n        }\n\n        [Fact]\n        public void TestApply()\n        {\n            UrlDir.UrlFile file = UrlBuilder.CreateFile(\"abc/def.cfg\");\n\n            ConfigNode config1 = new TestConfigNode(\"NODE\")\n            {\n                { \"foo\", \"bar\" },\n            };\n\n            ConfigNode config2 = new TestConfigNode(\"NODE\")\n            {\n                { \"foo\", \"bar\" },\n            };\n\n            ConfigNode config3 = new ConfigNode(\"NODE\");\n            ConfigNode config4 = new ConfigNode(\"NODE\");\n\n            INodeMatcher nodeMatcher = Substitute.For<INodeMatcher>();\n\n            nodeMatcher.IsMatch(config1).Returns(false);\n            nodeMatcher.IsMatch(config2).Returns(true);\n            nodeMatcher.IsMatch(config3).Returns(false);\n            nodeMatcher.IsMatch(config4).Returns(true);\n\n            EditPatch patch = new EditPatch(UrlBuilder.CreateConfig(\"ghi/jkl\", new TestConfigNode(\"@NODE\")\n            {\n                { \"@foo\", \"baz\" },\n                { \"pqr\", \"stw\" },\n            }), nodeMatcher, Substitute.For<IPassSpecifier>());\n\n            IProtoUrlConfig urlConfig1 = Substitute.For<IProtoUrlConfig>();\n            IProtoUrlConfig urlConfig2 = Substitute.For<IProtoUrlConfig>();\n            IProtoUrlConfig urlConfig3 = Substitute.For<IProtoUrlConfig>();\n            IProtoUrlConfig urlConfig4 = Substitute.For<IProtoUrlConfig>();\n\n            urlConfig1.Node.Returns(config1);\n            urlConfig2.Node.Returns(config2);\n            urlConfig3.Node.Returns(config3);\n            urlConfig4.Node.Returns(config4);\n\n            urlConfig1.UrlFile.Returns(file);\n            urlConfig2.UrlFile.Returns(file);\n            urlConfig3.UrlFile.Returns(file);\n            urlConfig4.UrlFile.Returns(file);\n\n            LinkedList<IProtoUrlConfig> configs = new LinkedList<IProtoUrlConfig>();\n            configs.AddLast(urlConfig1);\n            configs.AddLast(urlConfig2);\n            configs.AddLast(urlConfig3);\n            configs.AddLast(urlConfig4);\n\n            IPatchProgress progress = Substitute.For<IPatchProgress>();\n            IBasicLogger logger = Substitute.For<IBasicLogger>();\n\n            patch.Apply(configs, progress, logger);\n\n            IProtoUrlConfig[] newConfigs = configs.ToArray();\n\n            Assert.Equal(4, newConfigs.Length);\n\n            Assert.Same(urlConfig1, newConfigs[0]);\n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"foo\", \"bar\" },\n            }, newConfigs[0].Node);\n\n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"foo\", \"baz\" },\n                { \"pqr\", \"stw\" },\n            }, newConfigs[1].Node);\n            Assert.Same(file, newConfigs[1].UrlFile);\n\n            Assert.Same(urlConfig3, newConfigs[2]);\n            AssertNodesEqual(new ConfigNode(\"NODE\"), newConfigs[2].Node);\n\n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"pqr\", \"stw\" },\n            }, newConfigs[3].Node);\n            Assert.Same(file, newConfigs[3].UrlFile);\n\n            Received.InOrder(delegate\n            {\n                progress.ApplyingUpdate(urlConfig2, patch.UrlConfig);\n                progress.ApplyingUpdate(urlConfig4, patch.UrlConfig);\n            });\n\n            progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null);\n            progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null);\n\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null, null);\n        }\n\n        [Fact]\n        public void TestApply__Loop()\n        {\n            UrlDir.UrlFile file = UrlBuilder.CreateFile(\"abc/def.cfg\");\n\n            ConfigNode config = new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"000\" },\n                { \"aaa\", \"1\" },\n            };\n            \n            INodeMatcher nodeMatcher = Substitute.For<INodeMatcher>();\n\n            nodeMatcher.IsMatch(Arg.Is<ConfigNode>(node => int.Parse(node.GetValue(\"aaa\")) < 10)).Returns(true);\n\n            EditPatch patch = new EditPatch(UrlBuilder.CreateConfig(\"ghi/jkl\", new TestConfigNode(\"@NODE\")\n            {\n                { \"@aaa *\", \"2\" },\n                { \"bbb\", \"002\" },\n                new ConfigNode(\"MM_PATCH_LOOP\"),\n            }), nodeMatcher, Substitute.For<IPassSpecifier>());\n\n            IProtoUrlConfig urlConfig = Substitute.For<IProtoUrlConfig>();\n            urlConfig.Node.Returns(config);\n            urlConfig.UrlFile.Returns(file);\n            urlConfig.FullUrl.Returns(\"abc/def.cfg/NODE\");\n\n            LinkedList<IProtoUrlConfig> configs = new LinkedList<IProtoUrlConfig>();\n            configs.AddLast(urlConfig);\n\n            IPatchProgress progress = Substitute.For<IPatchProgress>();\n            IBasicLogger logger = Substitute.For<IBasicLogger>();\n\n            List<IProtoUrlConfig> modifiedUrlConfigs = new List<IProtoUrlConfig>();\n            progress.ApplyingUpdate(Arg.Do<IProtoUrlConfig>(url => modifiedUrlConfigs.Add(url)), patch.UrlConfig);\n\n            patch.Apply(configs, progress, logger);\n\n            Assert.Single(configs);\n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"000\" },\n                { \"aaa\", \"16\" },\n                { \"bbb\", \"002\" },\n                { \"bbb\", \"002\" },\n                { \"bbb\", \"002\" },\n                { \"bbb\", \"002\" },\n            }, configs.First.Value.Node);\n            Assert.Same(file, configs.First.Value.UrlFile);\n\n            Assert.Same(urlConfig, modifiedUrlConfigs[0]);\n            Assert.NotSame(urlConfig, modifiedUrlConfigs[1]);\n            Assert.NotSame(urlConfig, modifiedUrlConfigs[2]);\n            Assert.NotSame(urlConfig, modifiedUrlConfigs[3]);\n\n            Received.InOrder(delegate\n            {\n                logger.AssertInfo(\"Looping on ghi/jkl/@NODE to abc/def.cfg/NODE\");\n                progress.ApplyingUpdate(urlConfig, patch.UrlConfig);\n                progress.ApplyingUpdate(modifiedUrlConfigs[1], patch.UrlConfig);\n                progress.ApplyingUpdate(modifiedUrlConfigs[2], patch.UrlConfig);\n                progress.ApplyingUpdate(modifiedUrlConfigs[3], patch.UrlConfig);\n            });\n\n            progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null);\n            progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null);\n\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null, null);\n        }\n\n        [Fact]\n        public void TestApply__DatabaseConfigsNull()\n        {\n            EditPatch patch = new EditPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                patch.Apply(null, Substitute.For<IPatchProgress>(), Substitute.For<IBasicLogger>());\n            });\n\n            Assert.Equal(\"databaseConfigs\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestApply__ProgressNull()\n        {\n            EditPatch patch = new EditPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                patch.Apply(new LinkedList<IProtoUrlConfig>(), null, Substitute.For<IBasicLogger>());\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestApply__LoggerNull()\n        {\n            EditPatch patch = new EditPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), Substitute.For<INodeMatcher>(), Substitute.For<IPassSpecifier>());\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                patch.Apply(new LinkedList<IProtoUrlConfig>(), Substitute.For<IPatchProgress>(), null);\n            });\n\n            Assert.Equal(\"logger\", ex.ParamName);\n        }\n\n        private void AssertNodesEqual(ConfigNode expected, ConfigNode actual)\n        {\n            Assert.Equal(expected.ToString(), actual.ToString());\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Patches/InsertPatchTest.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Xunit;\nusing NSubstitute;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Logging;\nusing ModuleManager.Patches;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\n\nnamespace ModuleManagerTests.Patches\n{\n    public class InsertPatchTest\n    {\n        [Fact]\n        public void TestConstructor__UrlConfigNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new InsertPatch(null, \"A_NODE\", Substitute.For<IPassSpecifier>());\n            });\n\n            Assert.Equal(\"urlConfig\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__NodeTypeNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new InsertPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), null, Substitute.For<IPassSpecifier>());\n            });\n\n            Assert.Equal(\"nodeType\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__PassSpecifierNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new InsertPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), \"A_NODE\", null);\n            });\n\n            Assert.Equal(\"passSpecifier\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestUrlConfig()\n        {\n            UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode());\n            InsertPatch patch = new InsertPatch(urlConfig, \"A_NODE\", Substitute.For<IPassSpecifier>());\n\n            Assert.Same(urlConfig, patch.UrlConfig);\n        }\n\n        [Fact]\n        public void TestNodeType()\n        {\n            InsertPatch patch = new InsertPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), \"A_NODE\", Substitute.For<IPassSpecifier>());\n\n            Assert.Equal(\"A_NODE\", patch.NodeType);\n        }\n\n        [Fact]\n        public void TestPassSpecifier()\n        {\n            IPassSpecifier passSpecifier = Substitute.For<IPassSpecifier>();\n            InsertPatch patch = new InsertPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), \"A_NODE\", passSpecifier);\n\n            Assert.Same(passSpecifier, patch.PassSpecifier);\n        }\n        \n        [Fact]\n        public void TestCountsAsPatch()\n        {\n            InsertPatch patch = new InsertPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), \"A_NODE\", Substitute.For<IPassSpecifier>());\n            Assert.False(patch.CountsAsPatch);\n        }\n\n        [Fact]\n        public void TestApply()\n        {\n            UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def\", new TestConfigNode(\"A_NODE:NEEDS[someMod]:FOR[somePass]\")\n            {\n                { \"key1\", \"value1\" },\n                { \"key2\", \"value2\" },\n                new TestConfigNode(\"NODE_1\")\n                {\n                    { \"key3\", \"value3\" },\n                },\n                new TestConfigNode(\"NODE_2\")\n                {\n                    { \"key4\", \"value4\" },\n                },\n            });\n\n            InsertPatch patch = new InsertPatch(urlConfig, \"A_NODE\", Substitute.For<IPassSpecifier>());\n\n            LinkedList<IProtoUrlConfig> databaseConfigs = new LinkedList<IProtoUrlConfig>();\n\n            IProtoUrlConfig config1 = Substitute.For<IProtoUrlConfig>();\n            IProtoUrlConfig config2 = Substitute.For<IProtoUrlConfig>();\n\n            databaseConfigs.AddLast(config1);\n            databaseConfigs.AddLast(config2);\n\n            patch.Apply(databaseConfigs, Substitute.For<IPatchProgress>(), Substitute.For<IBasicLogger>());\n\n            IProtoUrlConfig[] databaseConfigsArray = databaseConfigs.ToArray();\n            Assert.Equal(3, databaseConfigsArray.Length);\n            Assert.Same(config1, databaseConfigsArray[0]);\n            Assert.Same(config2, databaseConfigsArray[1]);\n\n            Assert.Same(urlConfig.parent, databaseConfigsArray[2].UrlFile);\n            Assert.Equal(\"abc/def.cfg\", databaseConfigsArray[2].FileUrl);\n            Assert.Equal(\"A_NODE\", databaseConfigsArray[2].NodeType);\n            Assert.Equal(\"abc/def.cfg/A_NODE\", databaseConfigsArray[2].FullUrl);\n\n            Assert.NotSame(urlConfig.config, databaseConfigsArray[2].Node);\n            Assert.Equal(\"A_NODE\", databaseConfigsArray[2].Node.name);\n            Assert.Equal(\"A_NODE:NEEDS[someMod]:FOR[somePass]\", urlConfig.config.name); // make sure this hasn't been changed\n            Assert.Equal(2, databaseConfigsArray[2].Node.values.Count);\n            Assert.Equal(\"key1\", databaseConfigsArray[2].Node.values[0].name);\n            Assert.Equal(\"value1\", databaseConfigsArray[2].Node.values[0].value);\n            Assert.Equal(\"key2\", databaseConfigsArray[2].Node.values[1].name);\n            Assert.Equal(\"value2\", databaseConfigsArray[2].Node.values[1].value);\n            Assert.Equal(2, databaseConfigsArray[2].Node.nodes.Count);\n            Assert.Equal(\"NODE_1\", databaseConfigsArray[2].Node.nodes[0].name);\n            Assert.Equal(1, databaseConfigsArray[2].Node.nodes[0].values.Count);\n            Assert.Equal(\"key3\", databaseConfigsArray[2].Node.nodes[0].values[0].name);\n            Assert.Equal(\"value3\", databaseConfigsArray[2].Node.nodes[0].values[0].value);\n            Assert.Equal(0, databaseConfigsArray[2].Node.nodes[0].nodes.Count);\n            Assert.Equal(\"NODE_2\", databaseConfigsArray[2].Node.nodes[1].name);\n            Assert.Equal(1, databaseConfigsArray[2].Node.nodes[1].values.Count);\n            Assert.Equal(\"key4\", databaseConfigsArray[2].Node.nodes[1].values[0].name);\n            Assert.Equal(\"value4\", databaseConfigsArray[2].Node.nodes[1].values[0].value);\n            Assert.Equal(0, databaseConfigsArray[2].Node.nodes[1].nodes.Count);\n        }\n\n        [Fact]\n        public void TestApply__DatabaseConfigsNull()\n        {\n            InsertPatch patch = new InsertPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), \"A_NODE\", Substitute.For<IPassSpecifier>());\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                patch.Apply(null, Substitute.For<IPatchProgress>(), Substitute.For<IBasicLogger>());\n            });\n\n            Assert.Equal(\"configs\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestApply__ProgressNull()\n        {\n            InsertPatch patch = new InsertPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), \"A_NODE\", Substitute.For<IPassSpecifier>());\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                patch.Apply(new LinkedList<IProtoUrlConfig>(), null, Substitute.For<IBasicLogger>());\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestApply__LoggerNull()\n        {\n            InsertPatch patch = new InsertPatch(UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode()), \"A_NODE\", Substitute.For<IPassSpecifier>());\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                patch.Apply(new LinkedList<IProtoUrlConfig>(), Substitute.For<IPatchProgress>(), null);\n            });\n\n            Assert.Equal(\"logger\", ex.ParamName);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Patches/PassSpecifiers/AfterPassSpecifierTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests.Patches\n{\n    public class AfterPassSpecifierTest\n    {\n        public readonly UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"NODE\"));\n        public readonly INeedsChecker needsChecker = Substitute.For<INeedsChecker>();\n        public readonly IPatchProgress progress = Substitute.For<IPatchProgress>();\n        public readonly AfterPassSpecifier passSpecifier;\n\n        public AfterPassSpecifierTest()\n        {\n            passSpecifier = new AfterPassSpecifier(\"mod1\", urlConfig);\n        }\n\n        [Fact]\n        public void TestConstructor__ModNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new AfterPassSpecifier(null, urlConfig);\n            });\n\n            Assert.Equal(\"mod\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__ModEmpty()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new AfterPassSpecifier(\"\", urlConfig);\n            });\n\n            Assert.Equal(\"mod\", ex.ParamName);\n            Assert.Contains(\"can't be empty\", ex.Message);\n        }\n\n        [Fact]\n        public void TestConstructor__UrlConfigNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new AfterPassSpecifier(\"mod1\", null);\n            });\n\n            Assert.Equal(\"urlConfig\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__False()\n        {\n            needsChecker.CheckNeeds(\"mod1\").Returns(false);\n            Assert.False(passSpecifier.CheckNeeds(needsChecker, progress));\n\n            progress.Received().NeedsUnsatisfiedAfter(urlConfig);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__True()\n        {\n            needsChecker.CheckNeeds(\"mod1\").Returns(true);\n            Assert.True(passSpecifier.CheckNeeds(needsChecker, progress));\n\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__NeedsCheckerNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                passSpecifier.CheckNeeds(null, progress);\n            });\n\n            Assert.Equal(\"needsChecker\", ex.ParamName);\n\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__ProgressNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                passSpecifier.CheckNeeds(needsChecker, null);\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestDescriptor()\n        {\n            Assert.Equal(\":AFTER[MOD1]\", passSpecifier.Descriptor);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Patches/PassSpecifiers/BeforePassSpecifierTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests.Patches.PassSpecifiers\n{\n    public class BeforePassSpecifierTest\n    {\n        public readonly UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"NODE\"));\n        public readonly INeedsChecker needsChecker = Substitute.For<INeedsChecker>();\n        public readonly IPatchProgress progress = Substitute.For<IPatchProgress>();\n        public readonly BeforePassSpecifier passSpecifier;\n\n        public BeforePassSpecifierTest()\n        {\n            passSpecifier = new BeforePassSpecifier(\"mod1\", urlConfig);\n        }\n\n        [Fact]\n        public void TestConstructor__ModNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new BeforePassSpecifier(null, urlConfig);\n            });\n\n            Assert.Equal(\"mod\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__ModEmpty()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new BeforePassSpecifier(\"\", urlConfig);\n            });\n\n            Assert.Equal(\"mod\", ex.ParamName);\n            Assert.Contains(\"can't be empty\", ex.Message);\n        }\n\n        [Fact]\n        public void TestConstructor__UrlConfigNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new BeforePassSpecifier(\"mod1\", null);\n            });\n\n            Assert.Equal(\"urlConfig\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__False()\n        {\n            needsChecker.CheckNeeds(\"mod1\").Returns(false);\n            Assert.False(passSpecifier.CheckNeeds(needsChecker, progress));\n\n            progress.Received().NeedsUnsatisfiedBefore(urlConfig);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__True()\n        {\n            needsChecker.CheckNeeds(\"mod1\").Returns(true);\n            Assert.True(passSpecifier.CheckNeeds(needsChecker, progress));\n\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedBefore(null);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__NeedsCheckerNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                passSpecifier.CheckNeeds(null, progress);\n            });\n\n            Assert.Equal(\"needsChecker\", ex.ParamName);\n\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedBefore(null);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__ProgressNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                passSpecifier.CheckNeeds(needsChecker, null);\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestDescriptor()\n        {\n            Assert.Equal(\":BEFORE[MOD1]\", passSpecifier.Descriptor);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Patches/PassSpecifiers/FinalPassSpecifierTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing ModuleManager;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests.Patches\n{\n    public class FinalPassSpecifierrTest\n    {\n        public readonly INeedsChecker needsChecker = Substitute.For<INeedsChecker>();\n        public readonly IPatchProgress progress = Substitute.For<IPatchProgress>();\n        private readonly FinalPassSpecifier passSpecifier = new FinalPassSpecifier();\n\n        [Fact]\n        public void TestCheckNeeds()\n        {\n            Assert.True(passSpecifier.CheckNeeds(needsChecker, progress));\n        }\n\n        [Fact]\n        public void TestCheckNeeds__NeedsCheckerNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                passSpecifier.CheckNeeds(null, progress);\n            });\n\n            Assert.Equal(\"needsChecker\", ex.ParamName);\n\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__ProgressNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                passSpecifier.CheckNeeds(needsChecker, null);\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestDescriptor()\n        {\n            Assert.Equal(\":FINAL\", passSpecifier.Descriptor);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Patches/PassSpecifiers/FirstPassSpecifierTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing ModuleManager;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests.Patches\n{\n    public class FirstPassSpecifierTest\n    {\n        public readonly INeedsChecker needsChecker = Substitute.For<INeedsChecker>();\n        public readonly IPatchProgress progress = Substitute.For<IPatchProgress>();\n        private readonly FirstPassSpecifier passSpecifier = new FirstPassSpecifier();\n\n        [Fact]\n        public void TestCheckNeeds()\n        {\n            Assert.True(passSpecifier.CheckNeeds(needsChecker, progress));\n        }\n\n        [Fact]\n        public void TestCheckNeeds__NeedsCheckerNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                passSpecifier.CheckNeeds(null, progress);\n            });\n\n            Assert.Equal(\"needsChecker\", ex.ParamName);\n\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__ProgressNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                passSpecifier.CheckNeeds(needsChecker, null);\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestDescriptor()\n        {\n            Assert.Equal(\":FIRST\", passSpecifier.Descriptor);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Patches/PassSpecifiers/ForPassSpecifierTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests.Patches\n{\n    public class ForPassSpecifierTest\n    {\n        public readonly UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"NODE\"));\n        public readonly INeedsChecker needsChecker = Substitute.For<INeedsChecker>();\n        public readonly IPatchProgress progress = Substitute.For<IPatchProgress>();\n        public readonly ForPassSpecifier passSpecifier;\n\n        public ForPassSpecifierTest()\n        {\n            passSpecifier = new ForPassSpecifier(\"mod1\", urlConfig);\n        }\n\n        [Fact]\n        public void TestConstructor__ModNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new ForPassSpecifier(null, urlConfig);\n            });\n\n            Assert.Equal(\"mod\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__ModEmpty()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new ForPassSpecifier(\"\", urlConfig);\n            });\n\n            Assert.Equal(\"mod\", ex.ParamName);\n            Assert.Contains(\"can't be empty\", ex.Message);\n        }\n\n        [Fact]\n        public void TestConstructor__UrlConfigNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new ForPassSpecifier(\"mod1\", null);\n            });\n\n            Assert.Equal(\"urlConfig\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__False()\n        {\n            needsChecker.CheckNeeds(\"mod1\").Returns(false);\n            Assert.False(passSpecifier.CheckNeeds(needsChecker, progress));\n\n            progress.Received().NeedsUnsatisfiedFor(urlConfig);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__True()\n        {\n            needsChecker.CheckNeeds(\"mod1\").Returns(true);\n            Assert.True(passSpecifier.CheckNeeds(needsChecker, progress));\n\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedFor(null);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__NeedsCheckerNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                passSpecifier.CheckNeeds(null, progress);\n            });\n\n            Assert.Equal(\"needsChecker\", ex.ParamName);\n\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedFor(null);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__ProgressNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                passSpecifier.CheckNeeds(needsChecker, null);\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestDescriptor()\n        {\n            Assert.Equal(\":FOR[MOD1]\", passSpecifier.Descriptor);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Patches/PassSpecifiers/InsertPassSpecifierTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing ModuleManager;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests.Patches\n{\n    public class InsertPassSpecifierTest\n    {\n        public readonly INeedsChecker needsChecker = Substitute.For<INeedsChecker>();\n        public readonly IPatchProgress progress = Substitute.For<IPatchProgress>();\n        private readonly InsertPassSpecifier passSpecifier = new InsertPassSpecifier();\n\n        [Fact]\n        public void TestCheckNeeds()\n        {\n            Assert.True(passSpecifier.CheckNeeds(needsChecker, progress));\n        }\n\n        [Fact]\n        public void TestCheckNeeds__NeedsCheckerNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                passSpecifier.CheckNeeds(null, progress);\n            });\n\n            Assert.Equal(\"needsChecker\", ex.ParamName);\n\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__ProgressNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                passSpecifier.CheckNeeds(needsChecker, null);\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestDescriptor()\n        {\n            Assert.Equal(\":INSERT (initial)\", passSpecifier.Descriptor);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Patches/PassSpecifiers/LastPassSpecifierTest.cs",
    "content": "using System;\nusing Xunit;\nusing NSubstitute;\nusing ModuleManager;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests.Patches\n{\n    public class LastPassSpecifierTest\n    {\n        public readonly INeedsChecker needsChecker = Substitute.For<INeedsChecker>();\n        public readonly IPatchProgress progress = Substitute.For<IPatchProgress>();\n        public readonly LastPassSpecifier passSpecifier;\n\n        public LastPassSpecifierTest()\n        {\n            passSpecifier = new LastPassSpecifier(\"mod1\");\n        }\n\n        [Fact]\n        public void TestConstructor__ModNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new LastPassSpecifier(null);\n            });\n\n            Assert.Equal(\"mod\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__ModEmpty()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new LastPassSpecifier(\"\");\n            });\n\n            Assert.Equal(\"mod\", ex.ParamName);\n            Assert.Contains(\"can't be empty\", ex.Message);\n        }\n\n        [Fact]\n        public void TestCheckNeeds()\n        {\n            passSpecifier.CheckNeeds(needsChecker, progress);\n\n            needsChecker.DidNotReceiveWithAnyArgs().CheckNeeds(null);\n        }\n\n        [Fact]\n        public void TestDescriptor()\n        {\n            Assert.Equal(\":LAST[MOD1]\", passSpecifier.Descriptor);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Patches/PassSpecifiers/LegacyPassSpecifierTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing ModuleManager;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests.Patches\n{\n    public class LegacyPassSpecifierTest\n    {\n        public readonly INeedsChecker needsChecker = Substitute.For<INeedsChecker>();\n        public readonly IPatchProgress progress = Substitute.For<IPatchProgress>();\n        private readonly LegacyPassSpecifier passSpecifier = new LegacyPassSpecifier();\n\n        [Fact]\n        public void TestCheckNeeds()\n        {\n            Assert.True(passSpecifier.CheckNeeds(needsChecker, progress));\n        }\n\n        [Fact]\n        public void TestCheckNeeds__NeedsCheckerNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                passSpecifier.CheckNeeds(null, progress);\n            });\n\n            Assert.Equal(\"needsChecker\", ex.ParamName);\n\n            progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null);\n        }\n\n        [Fact]\n        public void TestCheckNeeds__ProgressNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                passSpecifier.CheckNeeds(needsChecker, null);\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestDescriptor()\n        {\n            Assert.Equal(\":LEGACY (default)\", passSpecifier.Descriptor);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Patches/PatchCompilerTest.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Xunit;\nusing NSubstitute;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Logging;\nusing ModuleManager.Patches;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests.Patches\n{\n    public class PatchCompilerTest\n    {\n        private readonly IPatchProgress progress = Substitute.For<IPatchProgress>();\n        private readonly IBasicLogger logger = Substitute.For<IBasicLogger>();\n        private readonly UrlDir.UrlFile file = UrlBuilder.CreateFile(\"abc/def.cfg\");\n        private readonly PatchCompiler patchCompiler = new PatchCompiler();\n\n        [Fact]\n        public void TestCompilePatch__Insert()\n        {\n            ProtoPatch protoPatch = new ProtoPatch(\n                UrlBuilder.CreateConfig(new TestConfigNode(\"NODEE\")\n                {\n                    { \"name\", \"foo\" },\n                    { \"bar\", \"bleh\" },\n                }, file),\n                Command.Insert,\n                \"NODE\",\n                \"foo\",\n                null,\n                \"#bar\",\n                Substitute.For<IPassSpecifier>()\n            );\n\n            InsertPatch patch = Assert.IsType<InsertPatch>(patchCompiler.CompilePatch(protoPatch));\n\n            Assert.Same(protoPatch.urlConfig, patch.UrlConfig);\n\n            LinkedList<IProtoUrlConfig> configs = new LinkedList<IProtoUrlConfig>();\n\n            patch.Apply(configs, progress, logger);\n\n            Assert.Single(configs);\n            Assert.NotSame(protoPatch.urlConfig.config, configs.First.Value.Node);\n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"foo\" },\n                { \"bar\", \"bleh\" },\n            }, configs.First.Value.Node);\n            Assert.Same(file, configs.First.Value.UrlFile);\n        }\n\n        [Fact]\n        public void TestCompilePatch__Edit()\n        {\n            ProtoPatch protoPatch = new ProtoPatch(\n                UrlBuilder.CreateConfig(\"ghi/jkl\", new TestConfigNode(\"@NODE\")\n                {\n                    { \"@bar\", \"bleh\" },\n                }),\n                Command.Edit,\n                \"NODE\",\n                \"foo\",\n                null,\n                \"#bar\",\n                Substitute.For<IPassSpecifier>()\n            );\n\n            EditPatch patch = Assert.IsType<EditPatch>(patchCompiler.CompilePatch(protoPatch));\n\n            Assert.Same(protoPatch.urlConfig, patch.UrlConfig);\n            AssertNodeMatcher(patch.NodeMatcher);\n\n            ConfigNode config = new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"foo\" },\n                { \"bar\", \"baz\" },\n            };\n\n            IProtoUrlConfig urlConfig = Substitute.For<IProtoUrlConfig>();\n            urlConfig.Node.Returns(config);\n            urlConfig.UrlFile.Returns(file);\n\n            LinkedList<IProtoUrlConfig> configs = new LinkedList<IProtoUrlConfig>();\n            configs.AddLast(urlConfig);\n\n            patch.Apply(configs, progress, logger);\n\n            AssertNoErrors();\n\n            progress.Received().ApplyingUpdate(urlConfig, protoPatch.urlConfig);\n\n            Assert.Single(configs);\n            Assert.NotSame(config, configs.First.Value.Node);\n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"foo\" },\n                { \"bar\", \"bleh\" },\n            }, configs.First.Value.Node);\n            Assert.Same(file, configs.First.Value.UrlFile);\n        }\n\n        [Fact]\n        public void TestCompilePatch__Copy()\n        {\n            ProtoPatch protoPatch = new ProtoPatch(\n                UrlBuilder.CreateConfig(\"ghi/jkl\", new TestConfigNode(\"+NODE\")\n                {\n                    { \"@name\", \"boo\" },\n                    { \"@bar\", \"bleh\" },\n                }),\n                Command.Copy,\n                \"NODE\",\n                \"foo\",\n                null,\n                \"#bar\",\n                Substitute.For<IPassSpecifier>()\n            );\n\n            CopyPatch patch = Assert.IsType<CopyPatch>(patchCompiler.CompilePatch(protoPatch));\n\n            Assert.Same(protoPatch.urlConfig, patch.UrlConfig);\n            AssertNodeMatcher(patch.NodeMatcher);\n\n            ConfigNode config = new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"foo\" },\n                { \"bar\", \"baz\" },\n            };\n\n            IProtoUrlConfig urlConfig = Substitute.For<IProtoUrlConfig>();\n            urlConfig.Node.Returns(config);\n            urlConfig.UrlFile.Returns(file);\n\n            LinkedList<IProtoUrlConfig> configs = new LinkedList<IProtoUrlConfig>();\n            configs.AddLast(urlConfig);\n\n            patch.Apply(configs, progress, logger);\n\n            AssertNoErrors();\n\n            progress.Received().ApplyingCopy(urlConfig, protoPatch.urlConfig);\n\n            IProtoUrlConfig[] newConfigs = configs.ToArray();\n\n            Assert.Equal(2, newConfigs.Length);\n            Assert.Same(config, newConfigs[0].Node);\n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"foo\" },\n                { \"bar\", \"baz\" },\n            }, newConfigs[0].Node);\n            AssertNodesEqual(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"boo\" },\n                { \"bar\", \"bleh\" },\n            }, newConfigs[1].Node);\n            Assert.Same(file, newConfigs[1].UrlFile);\n        }\n\n        [Fact]\n        public void TestCompilePatch__Delete()\n        {\n            ProtoPatch protoPatch = new ProtoPatch(\n                UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode(\"-NODE\")),\n                Command.Delete,\n                \"NODE\",\n                \"foo\",\n                null,\n                \"#bar\",\n                Substitute.For<IPassSpecifier>()\n            );\n\n            DeletePatch patch = Assert.IsType<DeletePatch>(patchCompiler.CompilePatch(protoPatch));\n\n            Assert.Same(protoPatch.urlConfig, patch.UrlConfig);\n            AssertNodeMatcher(patch.NodeMatcher);\n\n            IProtoUrlConfig urlConfig = Substitute.For<IProtoUrlConfig>();\n            urlConfig.Node.Returns(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"foo\" },\n                { \"bar\", \"baz\" },\n            });\n\n            LinkedList<IProtoUrlConfig> configs = new LinkedList<IProtoUrlConfig>();\n            configs.AddLast(urlConfig);\n\n            patch.Apply(configs, progress, logger);\n\n            AssertNoErrors();\n\n            progress.Received().ApplyingDelete(urlConfig, protoPatch.urlConfig);\n\n            Assert.Empty(configs);\n        }\n\n        [Fact]\n        public void TestCompilePatch__NullProtoPatch()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                patchCompiler.CompilePatch(null);\n            });\n\n            Assert.Equal(\"protoPatch\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestCompilePatch__InvalidCommand__Replace()\n        {\n            ProtoPatch protoPatch = new ProtoPatch(\n                UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode()),\n                Command.Replace,\n                \"NODE\",\n                \"foo\",\n                null,\n                \"#bar\",\n                Substitute.For<IPassSpecifier>()\n            );\n\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                patchCompiler.CompilePatch(protoPatch);\n            });\n\n            Assert.Equal(\"protoPatch\", ex.ParamName);\n            Assert.Contains(\"invalid command for a root node: Replace\", ex.Message);\n        }\n\n        [Fact]\n        public void TestCompilePatch__InvalidCommand__Create()\n        {\n            ProtoPatch protoPatch = new ProtoPatch(\n                UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode()),\n                Command.Create,\n                \"NODE\",\n                \"foo\",\n                null,\n                \"#bar\",\n                Substitute.For<IPassSpecifier>()\n            );\n\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                patchCompiler.CompilePatch(protoPatch);\n            });\n\n            Assert.Equal(\"protoPatch\", ex.ParamName);\n            Assert.Contains(\"invalid command for a root node: Create\", ex.Message);\n        }\n\n        [Fact]\n        public void TestCompilePatch__InvalidCommand__Rename()\n        {\n            ProtoPatch protoPatch = new ProtoPatch(\n                UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode()),\n                Command.Rename,\n                \"NODE\",\n                \"foo\",\n                null,\n                \"#bar\",\n                Substitute.For<IPassSpecifier>()\n            );\n\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                patchCompiler.CompilePatch(protoPatch);\n            });\n\n            Assert.Equal(\"protoPatch\", ex.ParamName);\n            Assert.Contains(\"invalid command for a root node: Rename\", ex.Message);\n        }\n\n        [Fact]\n        public void TestCompilePatch__InvalidCommand__Paste()\n        {\n            ProtoPatch protoPatch = new ProtoPatch(\n                UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode()),\n                Command.Paste,\n                \"NODE\",\n                \"foo\",\n                null,\n                \"#bar\",\n                Substitute.For<IPassSpecifier>()\n            );\n\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                patchCompiler.CompilePatch(protoPatch);\n            });\n\n            Assert.Equal(\"protoPatch\", ex.ParamName);\n            Assert.Contains(\"invalid command for a root node: Paste\", ex.Message);\n        }\n\n        [Fact]\n        public void TestCompilePatch__InvalidCommand__Special()\n        {\n            ProtoPatch protoPatch = new ProtoPatch(\n                UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode()),\n                Command.Special,\n                \"NODE\",\n                \"foo\",\n                null,\n                \"#bar\",\n                Substitute.For<IPassSpecifier>()\n            );\n\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                patchCompiler.CompilePatch(protoPatch);\n            });\n\n            Assert.Equal(\"protoPatch\", ex.ParamName);\n            Assert.Contains(\"invalid command for a root node: Special\", ex.Message);\n        }\n\n        private void AssertNodeMatcher(INodeMatcher matcher)\n        {\n            Assert.True(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"foo\" },\n                { \"bar\", \"baz\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"foo\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"boo\" },\n                { \"bar\", \"baz\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new ConfigNode(\"NODE\")));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NADE\")\n            {\n                { \"name\", \"foo\" },\n                { \"bar\", \"baz\" },\n            }));\n\n            Assert.False(matcher.IsMatch(new TestConfigNode(\"NODE\")\n            {\n                { \"name\", \"boo\" },\n                { \"bar\", \"baz\" },\n            }));\n        }\n\n        private void AssertNodesEqual(ConfigNode expected, ConfigNode actual)\n        {\n            Assert.Equal(expected.ToString(), actual.ToString());\n        }\n\n        private void AssertNoErrors()\n        {\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null, null);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Patches/ProtoPatchBuilderTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Collections;\nusing ModuleManager.Patches;\nusing ModuleManager.Patches.PassSpecifiers;\nusing ModuleManager.Progress;\nusing ModuleManager.Tags;\n\nnamespace ModuleManagerTests.Patches\n{\n    public class ProtoPatchBuilderTest\n    {\n        private readonly IPatchProgress progress;\n        private readonly ProtoPatchBuilder builder;\n        private readonly UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"NODE\"));\n\n        public ProtoPatchBuilderTest()\n        {\n            progress = Substitute.For<IPatchProgress>();\n            builder = new ProtoPatchBuilder(progress);\n        }\n\n        [Fact]\n        public void TestBuild__PrimaryValueNull()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<LegacyPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__PrimaryValue()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", \"stuff\", null));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Equal(\"stuff\", protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<LegacyPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__Needs()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"NEEDS\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Equal(\"stuff\", protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<LegacyPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__Needs__Case1()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"Needs\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Equal(\"stuff\", protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<LegacyPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__Needs__Case2()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"needs\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Equal(\"stuff\", protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<LegacyPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__Has()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"HAS\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Equal(\"stuff\", protoPatch.has);\n            Assert.IsType<LegacyPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__Has__Case1()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"Has\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Equal(\"stuff\", protoPatch.has);\n            Assert.IsType<LegacyPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__Has__Case2()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"has\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Equal(\"stuff\", protoPatch.has);\n            Assert.IsType<LegacyPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__First()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FIRST\", null, null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FirstPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__First__Case1()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"First\", null, null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FirstPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__First__Case2()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"first\", null, null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FirstPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__Before()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"BEFORE\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            BeforePassSpecifier passSpecifier = Assert.IsType<BeforePassSpecifier>(protoPatch.passSpecifier);\n            Assert.Equal(\"stuff\", passSpecifier.mod);\n            Assert.Same(urlConfig, passSpecifier.urlConfig);\n        }\n\n        [Fact]\n        public void TestBuild__Before__Case1()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"Before\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            BeforePassSpecifier passSpecifier = Assert.IsType<BeforePassSpecifier>(protoPatch.passSpecifier);\n            Assert.Equal(\"stuff\", passSpecifier.mod);\n            Assert.Same(urlConfig, passSpecifier.urlConfig);\n        }\n\n        [Fact]\n        public void TestBuild__Before__Case2()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"before\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            BeforePassSpecifier passSpecifier = Assert.IsType<BeforePassSpecifier>(protoPatch.passSpecifier);\n            Assert.Equal(\"stuff\", passSpecifier.mod);\n            Assert.Same(urlConfig, passSpecifier.urlConfig);\n        }\n\n        [Fact]\n        public void TestBuild__For()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FOR\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            ForPassSpecifier passSpecifier = Assert.IsType<ForPassSpecifier>(protoPatch.passSpecifier);\n            Assert.Equal(\"stuff\", passSpecifier.mod);\n            Assert.Same(urlConfig, passSpecifier.urlConfig);\n        }\n\n        [Fact]\n        public void TestBuild__For__Case1()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"For\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            ForPassSpecifier passSpecifier = Assert.IsType<ForPassSpecifier>(protoPatch.passSpecifier);\n            Assert.Equal(\"stuff\", passSpecifier.mod);\n            Assert.Same(urlConfig, passSpecifier.urlConfig);\n        }\n\n        [Fact]\n        public void TestBuild__For__Case2()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"for\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            ForPassSpecifier passSpecifier = Assert.IsType<ForPassSpecifier>(protoPatch.passSpecifier);\n            Assert.Equal(\"stuff\", passSpecifier.mod);\n            Assert.Same(urlConfig, passSpecifier.urlConfig);\n        }\n\n        [Fact]\n        public void TestBuild__After()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"AFTER\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            AfterPassSpecifier passSpecifier = Assert.IsType<AfterPassSpecifier>(protoPatch.passSpecifier);\n            Assert.Equal(\"stuff\", passSpecifier.mod);\n            Assert.Same(urlConfig, passSpecifier.urlConfig);\n        }\n\n        [Fact]\n        public void TestBuild__After__Case1()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"After\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            AfterPassSpecifier passSpecifier = Assert.IsType<AfterPassSpecifier>(protoPatch.passSpecifier);\n            Assert.Equal(\"stuff\", passSpecifier.mod);\n            Assert.Same(urlConfig, passSpecifier.urlConfig);\n        }\n\n        [Fact]\n        public void TestBuild__After__Case2()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"after\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            AfterPassSpecifier passSpecifier = Assert.IsType<AfterPassSpecifier>(protoPatch.passSpecifier);\n            Assert.Equal(\"stuff\", passSpecifier.mod);\n            Assert.Same(urlConfig, passSpecifier.urlConfig);\n        }\n\n        [Fact]\n        public void TestBuild__Last()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"LAST\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            LastPassSpecifier passSpecifier = Assert.IsType<LastPassSpecifier>(protoPatch.passSpecifier);\n            Assert.Equal(\"stuff\", passSpecifier.mod);\n        }\n\n        [Fact]\n        public void TestBuild__Last__Case1()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"Last\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            LastPassSpecifier passSpecifier = Assert.IsType<LastPassSpecifier>(protoPatch.passSpecifier);\n            Assert.Equal(\"stuff\", passSpecifier.mod);\n        }\n\n        [Fact]\n        public void TestBuild__Last__Case2()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"last\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            LastPassSpecifier passSpecifier = Assert.IsType<LastPassSpecifier>(protoPatch.passSpecifier);\n            Assert.Equal(\"stuff\", passSpecifier.mod);\n        }\n\n        [Fact]\n        public void TestBuild__Final()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FINAL\", null, null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FinalPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__Final__Case1()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"Final\", null, null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FinalPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__Final__Case2()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"final\", null, null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FinalPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__Insert__InsertPass()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"NEEDS\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Insert, tagList);\n\n            EnsureNoErrors();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Insert, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Equal(\"stuff\", protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<InsertPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__UrlConfigNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                builder.Build(null, Command.Edit, Substitute.For<ITagList>());\n            });\n\n            Assert.Equal(\"urlConfig\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestBuild__TagListNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                builder.Build(urlConfig, Command.Edit, null);\n            });\n\n            Assert.Equal(\"tagList\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestBuild__PrimaryValueEmpty()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", \"\", null));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"empty brackets detected on patch name: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<LegacyPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__NodeNameOnInsert()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", \"blah\", null));\n\n            Assert.Null(builder.Build(urlConfig, Command.Insert, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"name specifier detected on insert node (not a patch): abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__TrailerOnPrimaryTag()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", \"stuff\", \"otherStuff\"));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"unrecognized trailer: 'otherStuff' on: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Equal(\"stuff\", protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<LegacyPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__TrailerOnSomeTag()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"NEEDS\", \"stuff\", \"morestuff\")\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"unrecognized trailer: 'morestuff' on: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Equal(\"stuff\", protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<LegacyPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__MoreThanOneNeeds()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"NEEDS\", \"stuff\", null),\n                new Tag(\"NEEDS\", \"otherStuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"more than one :NEEDS tag detected, ignoring all but the first: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Equal(\"stuff\", protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<LegacyPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__NullNeeds()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"NEEDS\", null, null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Copy, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"empty :NEEDS tag detected: abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__EmptyNeeds()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"NEEDS\", \"\", null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Copy, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"empty :NEEDS tag detected: abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__MoreThanOneHas()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"HAS\", \"stuff\", null),\n                new Tag(\"HAS\", \"otherStuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"more than one :HAS tag detected, ignoring all but the first: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Equal(\"stuff\", protoPatch.has);\n            Assert.IsType<LegacyPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__NullHas()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"HAS\", null, null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Copy, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"empty :HAS tag detected: abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__EmptyHas()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"HAS\", \"\", null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Copy, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"empty :HAS tag detected: abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__HasOnInsert()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"HAS\", \"\", null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Insert, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \":HAS detected on insert node (not a patch): abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__BracketsOnFirst()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FIRST\", \"\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"value detected on :FIRST tag: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FirstPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__ValueOnFirst()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FIRST\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"value detected on :FIRST tag: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FirstPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__MoreThanOnePass__First()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FINAL\", null, null),\n                new Tag(\"FIRST\", null, null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"more than one pass specifier detected, ignoring all but the first: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FinalPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__PassSpecifierOnInsert__First()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FIRST\", null, null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Insert, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"pass specifier detected on insert node (not a patch): abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__NullBefore()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"BEFORE\", null, null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Copy, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"empty :BEFORE tag detected: abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__EmptyBefore()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"BEFORE\", \"\", null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Copy, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"empty :BEFORE tag detected: abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__MoreThanOnePass__Before()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FIRST\", null, null),\n                new Tag(\"BEFORE\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"more than one pass specifier detected, ignoring all but the first: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FirstPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__PassSpecifierOnInsert__Before()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"BEFORE\", \"mod1\", null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Insert, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"pass specifier detected on insert node (not a patch): abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__NullFor()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FOR\", null, null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Copy, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"empty :FOR tag detected: abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__EmptyFor()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FOR\", \"\", null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Copy, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"empty :FOR tag detected: abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__MoreThanOnePass__For()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FIRST\", null, null),\n                new Tag(\"FOR\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"more than one pass specifier detected, ignoring all but the first: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FirstPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__PassSpecifierOnInsert__For()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FOR\", \"mod1\", null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Insert, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"pass specifier detected on insert node (not a patch): abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__NullAfter()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"AFTER\", null, null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Copy, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"empty :AFTER tag detected: abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__EmptyAfter()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"AFTER\", \"\", null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Copy, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"empty :AFTER tag detected: abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__MoreThanOnePass__After()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FIRST\", null, null),\n                new Tag(\"AFTER\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"more than one pass specifier detected, ignoring all but the first: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FirstPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__PassSpecifierOnInsert__After()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"AFTER\", \"mod1\", null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Insert, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"pass specifier detected on insert node (not a patch): abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__NullLast()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"LAST\", null, null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Copy, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"empty :LAST tag detected: abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__EmptyLast()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"LAST\", \"\", null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Copy, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"empty :LAST tag detected: abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__MoreThanOnePass__Last()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FIRST\", null, null),\n                new Tag(\"LAST\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"more than one pass specifier detected, ignoring all but the first: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FirstPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__PassSpecifierOnInsert__Last()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"LAST\", \"mod1\", null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Insert, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"pass specifier detected on insert node (not a patch): abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__BracketsOnFinal()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FINAL\", \"\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"value detected on :FINAL tag: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FinalPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__ValueOnFinal()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FINAL\", \"stuff\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"value detected on :FINAL tag: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FinalPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__MoreThanOnePass__Final()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FIRST\", null, null),\n                new Tag(\"FINAL\", null, null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"more than one pass specifier detected, ignoring all but the first: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<FirstPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        [Fact]\n        public void TestBuild__PassSpecifierOnInsert__Final()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"FINAL\", null, null)\n            ));\n\n            Assert.Null(builder.Build(urlConfig, Command.Insert, tagList));\n\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.Received().Error(urlConfig, \"pass specifier detected on insert node (not a patch): abc/def/NODE\");\n            EnsureNoExceptions();\n        }\n\n        [Fact]\n        public void TestBuild__UnrecognizedTag()\n        {\n            ITagList tagList = Substitute.For<ITagList>();\n            tagList.PrimaryTag.Returns(new Tag(\"NODE\", null, null));\n            tagList.GetEnumerator().Returns(new ArrayEnumerator<Tag>(\n                new Tag(\"SOMESTUFF\", \"blah\", null)\n            ));\n\n            ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList);\n\n            progress.Received().Warning(urlConfig, \"unrecognized tag: 'SOMESTUFF' on: abc/def/NODE\");\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n\n            Assert.Same(urlConfig, protoPatch.urlConfig);\n            Assert.Equal(Command.Copy, protoPatch.command);\n            Assert.Equal(\"NODE\", protoPatch.nodeType);\n            Assert.Null(protoPatch.nodeName);\n            Assert.Null(protoPatch.needs);\n            Assert.Null(protoPatch.has);\n            Assert.IsType<LegacyPassSpecifier>(protoPatch.passSpecifier);\n        }\n\n        private void EnsureNoErrors()\n        {\n            progress.DidNotReceiveWithAnyArgs().Warning(null, null);\n            progress.DidNotReceiveWithAnyArgs().Error(null, null);\n            EnsureNoExceptions();\n        }\n\n        private void EnsureNoExceptions()\n        {\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null);\n            progress.DidNotReceiveWithAnyArgs().Exception(null, null, null);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Progress/PatchProgressTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing TestUtils;\nusing ModuleManager;\nusing ModuleManager.Logging;\nusing ModuleManager.Progress;\n\nnamespace ModuleManagerTests\n{\n    public class PatchProgressTest\n    {\n        private readonly IBasicLogger logger = Substitute.For<IBasicLogger>();\n        private readonly PatchProgress progress;\n\n        public PatchProgressTest()\n        {\n            progress = new PatchProgress(logger);\n        }\n\n        [Fact]\n        public void Test__Constructor__Nested()\n        {\n            IBasicLogger logger2 = Substitute.For<IBasicLogger>();\n            PatchProgress progress2 = new PatchProgress(progress, logger2);\n\n            Assert.Same(progress.Counter, progress2.Counter);\n\n            Assert.Equal(0, progress.Counter.patchedNodes);\n\n            IProtoUrlConfig original = Substitute.For<IProtoUrlConfig>();\n            original.FullUrl.Returns(\"abc/def.cfg/SOME_NODE\");\n            UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode(\"@SOME_NODE\"));\n\n            progress2.ApplyingUpdate(original, patch1);\n            Assert.Equal(1, progress.Counter.patchedNodes);\n            logger.AssertNoLog();\n            logger2.AssertInfo(\"Applying update ghi/jkl/@SOME_NODE to abc/def.cfg/SOME_NODE\");\n        }\n\n        [Fact]\n        public void TestPatchAdded()\n        {\n            Assert.Equal(0, progress.Counter.totalPatches);\n            progress.PatchAdded();\n            Assert.Equal(1, progress.Counter.totalPatches);\n            progress.PatchAdded();\n            Assert.Equal(2, progress.Counter.totalPatches);\n        }\n\n        [Fact]\n        public void TestApplyingUpdate()\n        {\n            IProtoUrlConfig original = Substitute.For<IProtoUrlConfig>();\n            original.FullUrl.Returns(\"abc/def.cfg/SOME_NODE\");\n            UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode(\"@SOME_NODE\"));\n            UrlDir.UrlConfig patch2 = UrlBuilder.CreateConfig(\"pqr/stu\", new ConfigNode(\"@SOME_NODE\"));\n\n            Assert.Equal(0, progress.Counter.patchedNodes);\n\n            progress.ApplyingUpdate(original, patch1);\n            Assert.Equal(1, progress.Counter.patchedNodes);\n            logger.AssertInfo(\"Applying update ghi/jkl/@SOME_NODE to abc/def.cfg/SOME_NODE\");\n\n            progress.ApplyingUpdate(original, patch2);\n            Assert.Equal(2, progress.Counter.patchedNodes);\n            logger.AssertInfo(\"Applying update pqr/stu/@SOME_NODE to abc/def.cfg/SOME_NODE\");\n        }\n\n        [Fact]\n        public void TesApplyingCopy()\n        {\n            IProtoUrlConfig original = Substitute.For<IProtoUrlConfig>();\n            original.FullUrl.Returns(\"abc/def.cfg/SOME_NODE\");\n            UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode(\"+SOME_NODE\"));\n            UrlDir.UrlConfig patch2 = UrlBuilder.CreateConfig(\"pqr/stu\", new ConfigNode(\"+SOME_NODE\"));\n\n            Assert.Equal(0, progress.Counter.patchedNodes);\n\n            progress.ApplyingCopy(original, patch1);\n            Assert.Equal(1, progress.Counter.patchedNodes);\n            logger.AssertInfo(\"Applying copy ghi/jkl/+SOME_NODE to abc/def.cfg/SOME_NODE\");\n\n            progress.ApplyingCopy(original, patch2);\n            Assert.Equal(2, progress.Counter.patchedNodes);\n            logger.AssertInfo(\"Applying copy pqr/stu/+SOME_NODE to abc/def.cfg/SOME_NODE\");\n        }\n\n        [Fact]\n        public void TesApplyingDelete()\n        {\n            IProtoUrlConfig original = Substitute.For<IProtoUrlConfig>();\n            original.FullUrl.Returns(\"abc/def.cfg/SOME_NODE\");\n            UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode(\"!SOME_NODE\"));\n            UrlDir.UrlConfig patch2 = UrlBuilder.CreateConfig(\"pqr/stu\", new ConfigNode(\"!SOME_NODE\"));\n\n            Assert.Equal(0, progress.Counter.patchedNodes);\n\n            progress.ApplyingDelete(original, patch1);\n            Assert.Equal(1, progress.Counter.patchedNodes);\n            logger.AssertInfo(\"Applying delete ghi/jkl/!SOME_NODE to abc/def.cfg/SOME_NODE\");\n\n            progress.ApplyingDelete(original, patch2);\n            Assert.Equal(2, progress.Counter.patchedNodes);\n            logger.AssertInfo(\"Applying delete pqr/stu/!SOME_NODE to abc/def.cfg/SOME_NODE\");\n        }\n\n        [Fact]\n        public void TestPatchApplied()\n        {\n            int eventCounter = 0;\n            progress.OnPatchApplied.Add(() => eventCounter++);\n            Assert.Equal(0, progress.Counter.appliedPatches);\n            progress.PatchApplied();\n            Assert.Equal(1, progress.Counter.appliedPatches);\n            Assert.Equal(1, eventCounter);\n            progress.PatchApplied();\n            Assert.Equal(2, progress.Counter.appliedPatches);\n            Assert.Equal(2, eventCounter);\n        }\n\n        [Fact]\n        public void TestNeedsUnsatisfiedRoot()\n        {\n            UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"SOME_NODE\"));\n            UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode(\"SOME_OTHER_NODE\"));\n\n            Assert.Equal(0, progress.Counter.needsUnsatisfied);\n\n            progress.NeedsUnsatisfiedRoot(config1);\n            Assert.Equal(1, progress.Counter.needsUnsatisfied);\n            logger.AssertInfo(\"Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its NEEDS\");\n\n            progress.NeedsUnsatisfiedRoot(config2);\n            Assert.Equal(2, progress.Counter.needsUnsatisfied);\n            logger.AssertInfo(\"Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its NEEDS\");\n        }\n\n        [Fact]\n        public void TestNeedsUnsatisfiedNode()\n        {\n            UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"SOME_NODE\"));\n            UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode(\"SOME_OTHER_NODE\"));\n\n            Assert.Equal(0, progress.Counter.needsUnsatisfied);\n\n            progress.NeedsUnsatisfiedNode(config1, \"SOME/NODE/PATH/SOME_CHILD_NODE\");\n            Assert.Equal(0, progress.Counter.needsUnsatisfied);\n            logger.AssertInfo(\"Deleting node in file abc/def subnode: SOME/NODE/PATH/SOME_CHILD_NODE as it can't satisfy its NEEDS\");\n\n            progress.NeedsUnsatisfiedNode(config2, \"SOME/NODE/PATH/SOME_OTHER_CHILD_NODE\");\n            Assert.Equal(0, progress.Counter.needsUnsatisfied);\n            logger.AssertInfo(\"Deleting node in file ghi/jkl subnode: SOME/NODE/PATH/SOME_OTHER_CHILD_NODE as it can't satisfy its NEEDS\");\n        }\n\n        [Fact]\n        public void TestNeedsUnsatisfiedValue()\n        {\n            UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"SOME_NODE\"));\n            UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode(\"SOME_OTHER_NODE\"));\n\n            Assert.Equal(0, progress.Counter.needsUnsatisfied);\n\n            progress.NeedsUnsatisfiedValue(config1, \"SOME/NODE/PATH/some_value\");\n            Assert.Equal(0, progress.Counter.needsUnsatisfied);\n            logger.AssertInfo(\"Deleting value in file abc/def value: SOME/NODE/PATH/some_value as it can't satisfy its NEEDS\");\n\n            progress.NeedsUnsatisfiedValue(config2, \"SOME/NODE/PATH/some_other_value\");\n            Assert.Equal(0, progress.Counter.needsUnsatisfied);\n            logger.AssertInfo(\"Deleting value in file ghi/jkl value: SOME/NODE/PATH/some_other_value as it can't satisfy its NEEDS\");\n        }\n\n        [Fact]\n        public void TestNeedsUnsatisfiedBefore()\n        {\n            UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"SOME_NODE\"));\n            UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode(\"SOME_OTHER_NODE\"));\n\n            Assert.Equal(0, progress.Counter.needsUnsatisfied);\n\n            progress.NeedsUnsatisfiedBefore(config1);\n            Assert.Equal(1, progress.Counter.needsUnsatisfied);\n            logger.AssertInfo(\"Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its BEFORE\");\n\n            progress.NeedsUnsatisfiedBefore(config2);\n            Assert.Equal(2, progress.Counter.needsUnsatisfied);\n            logger.AssertInfo(\"Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its BEFORE\");\n        }\n\n        [Fact]\n        public void TestNeedsUnsatisfiedFor()\n        {\n            UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"SOME_NODE\"));\n            UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode(\"SOME_OTHER_NODE\"));\n\n            Assert.Equal(0, progress.Counter.needsUnsatisfied);\n\n            progress.NeedsUnsatisfiedFor(config1);\n            Assert.Equal(1, progress.Counter.needsUnsatisfied);\n            logger.AssertWarning(\"Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its FOR (this shouldn't happen)\");\n\n            progress.NeedsUnsatisfiedFor(config2);\n            Assert.Equal(2, progress.Counter.needsUnsatisfied);\n            logger.AssertWarning(\"Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its FOR (this shouldn't happen)\");\n        }\n\n        [Fact]\n        public void TestNeedsUnsatisfiedAfter()\n        {\n            UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"SOME_NODE\"));\n            UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(\"ghi/jkl\", new ConfigNode(\"SOME_OTHER_NODE\"));\n\n            Assert.Equal(0, progress.Counter.needsUnsatisfied);\n\n            progress.NeedsUnsatisfiedAfter(config1);\n            Assert.Equal(1, progress.Counter.needsUnsatisfied);\n            logger.AssertInfo(\"Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its AFTER\");\n\n            progress.NeedsUnsatisfiedAfter(config2);\n            Assert.Equal(2, progress.Counter.needsUnsatisfied);\n            logger.AssertInfo(\"Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its AFTER\");\n        }\n\n        [Fact]\n        public void TestStartingPass()\n        {\n            EventData<IPass>.OnEvent onEvent = Substitute.For<EventData<IPass>.OnEvent>();\n            progress.OnPassStarted.Add(onEvent);\n            IPass pass1 = Substitute.For<IPass>();\n            pass1.Name.Returns(\":SOME_PASS\");\n\n            progress.PassStarted(pass1);\n\n            logger.AssertInfo(\":SOME_PASS pass\");\n            onEvent.Received()(pass1);\n        }\n\n        [Fact]\n        public void TestStartingPass__NullArgument()\n        {\n            EventData<IPass>.OnEvent onEvent = Substitute.For<EventData<IPass>.OnEvent>();\n            progress.OnPassStarted.Add(onEvent);\n\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                progress.PassStarted(null);\n            });\n\n            Assert.Equal(\"pass\", ex.ParamName);\n\n            logger.AssertNoLog();\n            onEvent.DidNotReceiveWithAnyArgs()(null);\n        }\n\n        [Fact]\n        public void TestWarning()\n        {\n            UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"SOME_NODE\"));\n            UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"SOME_OTHER_NODE\"));\n\n            Assert.Equal(0, progress.Counter.warnings);\n\n            progress.Warning(config1, \"I'm warning you\");\n            Assert.Equal(1, progress.Counter.warnings);\n            Assert.Equal(1, progress.Counter.warningFiles[\"abc/def.cfg\"]);\n            logger.AssertWarning(\"I'm warning you\");\n\n            progress.Warning(config2, \"You should probably pay attention to this\");\n            Assert.Equal(2, progress.Counter.warnings);\n            Assert.Equal(2, progress.Counter.warningFiles[\"abc/def.cfg\"]);\n            logger.AssertWarning(\"You should probably pay attention to this\");\n        }\n\n        [Fact]\n        public void TestError()\n        {\n            Assert.Equal(0, progress.Counter.errors);\n\n            progress.Error(\"An error message no one is going to read\");\n            Assert.Equal(1, progress.Counter.errors);\n\n            progress.Error(\"Maybe someone will read this one\");\n            Assert.Equal(2, progress.Counter.errors);\n        }\n\n        [Fact]\n        public void TestError__Config()\n        {\n            UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"SOME_NODE\"));\n            UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"SOME_OTHER_NODE\"));\n\n            Assert.Equal(0, progress.Counter.errors);\n            Assert.False(progress.Counter.errorFiles.ContainsKey(\"abc/def.cfg\"));\n\n            progress.Error(config1, \"An error message no one is going to read\");\n            Assert.Equal(1, progress.Counter.errors);\n            Assert.Equal(1, progress.Counter.errorFiles[\"abc/def.cfg\"]);\n            logger.AssertError(\"An error message no one is going to read\");\n\n            progress.Error(config2, \"Maybe someone will read this one\");\n            Assert.Equal(2, progress.Counter.errors);\n            Assert.Equal(2, progress.Counter.errorFiles[\"abc/def.cfg\"]);\n            logger.AssertError(\"Maybe someone will read this one\");\n        }\n\n        [Fact]\n        public void TestException()\n        {\n            Exception e1 = new Exception();\n            Exception e2 = new Exception();\n\n            Assert.Equal(0, progress.Counter.exceptions);\n\n            progress.Exception(\"An exception was thrown\", e1);\n            Assert.Equal(1, progress.Counter.exceptions);\n            logger.AssertException(\"An exception was thrown\", e1);\n\n            progress.Exception(\"An exception was tossed\", e2);\n            Assert.Equal(2, progress.Counter.exceptions);\n            logger.AssertException(\"An exception was tossed\", e2);\n        }\n\n        [Fact]\n        public void TestException__Url()\n        {\n            UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"SOME_NODE\"));\n            Exception e1 = new Exception();\n            UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(\"abc/def\", new ConfigNode(\"SOME_OTHER_NODE\"));\n            Exception e2 = new Exception();\n\n            Assert.Equal(0, progress.Counter.exceptions);\n            Assert.False(progress.Counter.errorFiles.ContainsKey(\"abc/def.cfg\"));\n\n            progress.Exception(config1, \"An exception was thrown\", e1);\n            Assert.Equal(1, progress.Counter.exceptions);\n            Assert.Equal(1, progress.Counter.errorFiles[\"abc/def.cfg\"]);\n            logger.AssertException(\"An exception was thrown\", e1);\n\n            progress.Exception(config2, \"An exception was tossed\", e2);\n            Assert.Equal(2, progress.Counter.exceptions);\n            Assert.Equal(2, progress.Counter.errorFiles[\"abc/def.cfg\"]);\n            logger.AssertException(\"An exception was tossed\", e2);\n        }\n\n        [Fact]\n        public void TestProgressFraction()\n        {\n            Assert.Equal(0, progress.ProgressFraction);\n\n            progress.Counter.needsUnsatisfied.Increment();\n            progress.Counter.needsUnsatisfied.Increment();\n\n            progress.Counter.totalPatches.Increment();\n            progress.Counter.totalPatches.Increment();\n            progress.Counter.totalPatches.Increment();\n            progress.Counter.totalPatches.Increment();\n\n            Assert.Equal(0, progress.ProgressFraction);\n\n            progress.Counter.appliedPatches.Increment();\n\n            Assert.Equal(0.25, progress.ProgressFraction);\n\n            progress.Counter.appliedPatches.Increment();\n\n            Assert.Equal(0.5, progress.ProgressFraction);\n\n            progress.Counter.appliedPatches.Increment();\n\n            Assert.Equal(0.75, progress.ProgressFraction);\n\n            progress.Counter.appliedPatches.Increment();\n\n            Assert.Equal(1, progress.ProgressFraction);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled through the following\n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyTitle(\"ModuleManagerTests\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"ModuleManagerTests\")]\n[assembly: AssemblyCopyright(\"Copyright ©  2017\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible\n// to COM components.  If you need to access a type in this assembly from\n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n// The following GUID is for the ID of the typelib if this project is exposed to COM\n[assembly: Guid(\"bc2a08c8-64ef-4823-a40b-8889c1ccfd75\")]\n\n// Version information for an assembly consists of the following four values:\n//\n//      Major Version\n//      Minor Version\n//      Build Number\n//      Revision\n//\n// You can specify all the values or you can default the Build and Revision Numbers\n// by using the '*' as shown below:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"1.0.0.0\")]\n[assembly: AssemblyFileVersion(\"1.0.0.0\")]\n"
  },
  {
    "path": "ModuleManagerTests/ProtoUrlConfigTest.cs",
    "content": "using System;\nusing Xunit;\nusing TestUtils;\nusing ModuleManager;\n\nnamespace ModuleManagerTests\n{\n    public class ProtoUrlConfigTest\n    {\n        [Fact]\n        public void TestContructor__UrlFileNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new ProtoUrlConfig(null, new ConfigNode());\n            });\n\n            Assert.Equal(\"urlFile\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestContructor__NodeNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new ProtoUrlConfig(UrlBuilder.CreateFile(\"foo/bar\"), null);\n            });\n\n            Assert.Equal(\"node\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestUrlFile()\n        {\n            UrlDir.UrlFile urlFile = UrlBuilder.CreateFile(\"abc/def.cfg\");\n            ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(urlFile, new ConfigNode());\n\n            Assert.Same(urlFile, protoUrlConfig.UrlFile);\n        }\n\n        [Fact]\n        public void TestNode()\n        {\n            ConfigNode node = new ConfigNode(\"NODE\");\n            ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile(\"foo/bar\"), node);\n\n            Assert.Same(node, protoUrlConfig.Node);\n        }\n\n        [Fact]\n        public void TestFileUrl()\n        {\n            ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile(\"abc/def.cfg\"), new ConfigNode());\n\n            Assert.Equal(\"abc/def.cfg\", protoUrlConfig.FileUrl);\n        }\n\n        [Fact]\n        public void TestNodeType()\n        {\n            ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile(\"abc/def\"), new ConfigNode(\"SOME_NODE\"));\n\n            Assert.Equal(\"SOME_NODE\", protoUrlConfig.NodeType);\n        }\n\n        [Fact]\n        public void TestFullUrl()\n        {\n            ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile(\"abc/def.cfg\"), new ConfigNode(\"SOME_NODE\"));\n\n            Assert.Equal(\"abc/def.cfg/SOME_NODE\", protoUrlConfig.FullUrl);\n        }\n\n        [Fact]\n        public void TestFullUrl__NameValue()\n        {\n            ConfigNode node = new TestConfigNode(\"SOME_NODE\")\n            {\n                { \"name\", \"some_value\" },\n            };\n\n            ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile(\"abc/def.cfg\"), node);\n\n            Assert.Equal(\"abc/def.cfg/SOME_NODE[some_value]\", protoUrlConfig.FullUrl);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Tags/TagListParserTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing NSubstitute;\nusing TestUtils;\nusing ModuleManager.Progress;\nusing ModuleManager.Tags;\n\nnamespace ModuleManagerTests.Tags\n{\n    public class TagListParserTest\n    {\n        private readonly IPatchProgress progress = Substitute.For<IPatchProgress>();\n        private readonly TagListParser tagListParser;\n        private readonly UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(\"abc/def.cfg\", new ConfigNode(\"BLAH\"));\n\n        public TagListParserTest()\n        {\n            tagListParser = new TagListParser(progress);\n        }\n\n        [Fact]\n        public void TestConstructor__ProgressNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new TagListParser(null);\n            });\n\n            Assert.Equal(\"progress\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestParse__OnlyPrimaryKey()\n        {\n            ITagList tagList = tagListParser.Parse(\"01\", urlConfig);\n            Assert.Equal(new Tag(\"01\", null, null), tagList.PrimaryTag);\n            Assert.Empty(tagList);\n        }\n\n        [Fact]\n        public void TestParse__OnlyPrimaryKeyAndValue()\n        {\n            ITagList tagList = tagListParser.Parse(\"01[02]\", urlConfig);\n            Assert.Equal(new Tag(\"01\", \"02\", null), tagList.PrimaryTag);\n            Assert.Empty(tagList);\n        }\n\n        [Fact]\n        public void TestParse__OnlyPrimaryKeyValueAndTrailer()\n        {\n            ITagList tagList = tagListParser.Parse(\"01[02]03\", urlConfig);\n            Assert.Equal(new Tag(\"01\", \"02\", \"03\"), tagList.PrimaryTag);\n            Assert.Empty(tagList);\n        }\n\n        [Fact]\n        public void TestParse__OnlyPrimaryKeyValueAndTrailer__ValueHasSomeStuff()\n        {\n            ITagList tagList = tagListParser.Parse(\"01[02:[03:04[05]]]06\", urlConfig);\n            Assert.Equal(new Tag(\"01\", \"02:[03:04[05]]\", \"06\"), tagList.PrimaryTag);\n            Assert.Empty(tagList);\n        }\n\n        [Fact]\n        public void TestParse__TagWithOnlyKey()\n        {\n            ITagList tagList = tagListParser.Parse(\"01[02]03:04:05\", urlConfig);\n            Assert.Equal(new Tag(\"01\", \"02\", \"03\"), tagList.PrimaryTag);\n            Assert.Equal(new[] {\n                new Tag(\"04\", null, null),\n                new Tag(\"05\", null, null),\n            }, tagList);\n        }\n\n        [Fact]\n        public void TestParse__TagWithOnlyKeyAndValue()\n        {\n            ITagList tagList = tagListParser.Parse(\"01[02]03:04[05]:06[07]\", urlConfig);\n            Assert.Equal(new Tag(\"01\", \"02\", \"03\"), tagList.PrimaryTag);\n            Assert.Equal(new[] {\n                new Tag(\"04\", \"05\", null),\n                new Tag(\"06\", \"07\", null),\n            }, tagList);\n        }\n\n        [Fact]\n        public void TestParse__FullTags()\n        {\n            ITagList tagList = tagListParser.Parse(\"01[02]03:04[05]06:07[08]09\", urlConfig);\n            Assert.Equal(new Tag(\"01\", \"02\", \"03\"), tagList.PrimaryTag);\n            Assert.Equal(new[] {\n                new Tag(\"04\", \"05\", \"06\"),\n                new Tag(\"07\", \"08\", \"09\"),\n            }, tagList);\n        }\n\n        [Fact]\n        public void TestParse__MixedTags()\n        {\n            ITagList tagList = tagListParser.Parse(\"01[02]03:04:05[06]:07[08]09\", urlConfig);\n            Assert.Equal(new Tag(\"01\", \"02\", \"03\"), tagList.PrimaryTag);\n            Assert.Equal(new[] {\n                new Tag(\"04\", null, null),\n                new Tag(\"05\", \"06\", null),\n                new Tag(\"07\", \"08\", \"09\"),\n            }, tagList);\n        }\n\n        [Fact]\n        public void TestParse__StringNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                tagListParser.Parse(null, urlConfig);\n            });\n\n            Assert.Equal(\"toParse\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestParse__UrlConfigNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                tagListParser.Parse(\"BLAH\", null);\n            });\n\n            Assert.Equal(\"urlConfig\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestParse__Empty()\n        {\n            FormatException ex = Assert.Throws<FormatException>(delegate\n            {\n                tagListParser.Parse(\"\", urlConfig);\n            });\n\n            Assert.Equal(\"can't create tag list from empty string\", ex.Message);\n        }\n\n        [Fact]\n        public void TestParse__StartsWithOpenBracket()\n        {\n            FormatException ex = Assert.Throws<FormatException>(delegate\n            {\n                tagListParser.Parse(\"[stuff]\", urlConfig);\n            });\n\n            Assert.Equal(\"can't create tag list beginning with [\", ex.Message);\n        }\n\n        [Fact]\n        public void TestParse__StartsWithColon()\n        {\n            FormatException ex = Assert.Throws<FormatException>(delegate\n            {\n                tagListParser.Parse(\":stuff\", urlConfig);\n            });\n\n            Assert.Equal(\"can't create tag list beginning with :\", ex.Message);\n        }\n\n        [Fact]\n        public void TestParse__EndsWithColon()\n        {\n            ITagList tagList = tagListParser.Parse(\"stuff:blah::\", urlConfig);\n\n            progress.Received().Warning(urlConfig, \"trailing : detected\");\n\n            Assert.Equal(new Tag(\"stuff\", null, null), tagList.PrimaryTag);\n            Assert.Equal(new[] {\n                new Tag(\"blah\", null, null),\n            }, tagList);\n        }\n\n        [Fact]\n        public void TestParse__ClosingBracketInPrimaryKey()\n        {\n            FormatException ex = Assert.Throws<FormatException>(delegate\n            {\n                tagListParser.Parse(\"abc]def\", urlConfig);\n            });\n\n            Assert.Equal(\"encountered closing bracket in primary key\", ex.Message);\n        }\n\n        [Fact]\n        public void TestParse__PrimaryValueHasNoClosingBracket()\n        {\n            FormatException ex = Assert.Throws<FormatException>(delegate\n            {\n                tagListParser.Parse(\"abc[def[ghi]jkl\", urlConfig);\n            });\n\n            Assert.Equal(\"reached end of the tag list without encountering a close bracket\", ex.Message);\n        }\n\n        [Fact]\n        public void TestParse__OpeningBracketInPrimaryTrailer()\n        {\n            FormatException ex = Assert.Throws<FormatException>(delegate\n            {\n                tagListParser.Parse(\"abc[def]ghi[jkl]\", urlConfig);\n            });\n\n            Assert.Equal(\"encountered opening bracket in primary trailer\", ex.Message);\n        }\n\n        [Fact]\n        public void TestParse__ClosingBracketInPrimaryTrailer()\n        {\n            FormatException ex = Assert.Throws<FormatException>(delegate\n            {\n                tagListParser.Parse(\"abc[def]ghi]jkl\", urlConfig);\n            });\n\n            Assert.Equal(\"encountered closing bracket in primary trailer\", ex.Message);\n        }\n\n        [Fact]\n        public void TestParse__TagStartsWithOpenBracket()\n        {\n            FormatException ex = Assert.Throws<FormatException>(delegate\n            {\n                tagListParser.Parse(\"abc:def:[ghi]\", urlConfig);\n            });\n\n            Assert.Equal(\"tag can't start with [\", ex.Message);\n        }\n\n        [Fact]\n        public void TestParse__TagStartsWithColon()\n        {\n            ITagList tagList = tagListParser.Parse(\"abc:def::ghi\", urlConfig);\n\n            progress.Received().Warning(urlConfig, \"extra : detected\");\n\n            Assert.Equal(new Tag(\"abc\", null, null), tagList.PrimaryTag);\n            Assert.Equal(new[] {\n                new Tag(\"def\", null, null),\n                new Tag(\"ghi\", null, null),\n            }, tagList);\n        }\n\n        [Fact]\n        public void TestParse__ClosingBracketInKey()\n        {\n            FormatException ex = Assert.Throws<FormatException>(delegate\n            {\n                tagListParser.Parse(\"abc:def:ghi]jkl\", urlConfig);\n            });\n\n            Assert.Equal(\"encountered closing bracket in key\", ex.Message);\n        }\n\n        [Fact]\n        public void TestParse__ValueHasNoClosingBracket()\n        {\n            FormatException ex = Assert.Throws<FormatException>(delegate\n            {\n                tagListParser.Parse(\"abc:def:ghi[jkl[mno]pqr\", urlConfig);\n            });\n\n            Assert.Equal(\"reached end of the tag list without encountering a close bracket\", ex.Message);\n        }\n\n        [Fact]\n        public void TestParse__OpeningBracketInTrailer()\n        {\n            FormatException ex = Assert.Throws<FormatException>(delegate\n            {\n                tagListParser.Parse(\"abc:def:ghi[jkl]mno[pqr]\", urlConfig);\n            });\n\n            Assert.Equal(\"encountered opening bracket in trailer\", ex.Message);\n        }\n\n        [Fact]\n        public void TestParse__ClosingBracketInTrailer()\n        {\n            FormatException ex = Assert.Throws<FormatException>(delegate\n            {\n                tagListParser.Parse(\"abc:def:ghi[jkl]mno]pqr\", urlConfig);\n            });\n\n            Assert.Equal(\"encountered closing bracket in trailer\", ex.Message);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Tags/TagListTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing ModuleManager.Tags;\n\nnamespace ModuleManagerTests.Tags\n{\n    public class TagListTest\n    {\n        [Fact]\n        public void TestPrimaryTag()\n        {\n            Tag primaryTag = new Tag(\"stuff\", null, null);\n            TagList tagList = new TagList(primaryTag, new Tag[0]);\n\n            Assert.Equal(primaryTag, tagList.PrimaryTag);\n        }\n\n        [Fact]\n        public void TestEnumeration()\n        {\n            Tag primaryTag = new Tag(\"stuff\", null, null);\n            Tag tag1 = new Tag(\"tag1\", null, null);\n            Tag tag2 = new Tag(\"tag2\", null, null);\n\n            Tag[] tags = new Tag[] { tag1, tag2 };\n            TagList tagList = new TagList(primaryTag, tags);\n\n            tags[0] = new Tag(\"tag3\", null, null);\n\n            Assert.Equal(new[] { tag1, tag2 }, tagList);\n        }\n\n        [Fact]\n        public void TestConstructor__TagsNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new TagList(new Tag(\"blah\", null, null), null);\n            });\n\n            Assert.Equal(\"tags\", ex.ParamName);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Tags/TagTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing ModuleManager.Tags;\n\nnamespace ModuleManagerTests.Tags\n{\n    public class TagTest\n    {\n        [Fact]\n        public void Test__OnlyKey()\n        {\n            Tag tag = new Tag(\"key\", null, null);\n\n            Assert.Equal(\"key\", tag.key);\n            Assert.Null(tag.value);\n            Assert.Null(tag.trailer);\n        }\n\n        [Fact]\n        public void Test__KeyAndValue()\n        {\n            Tag tag = new Tag(\"key\", \"value\", null);\n\n            Assert.Equal(\"key\", tag.key);\n            Assert.Equal(\"value\", tag.value);\n            Assert.Null(tag.trailer);\n        }\n\n        [Fact]\n        public void Test__KeyAndEmptyValue()\n        {\n            Tag tag = new Tag(\"key\", \"\", null);\n\n            Assert.Equal(\"key\", tag.key);\n            Assert.Equal(\"\", tag.value);\n            Assert.Null(tag.trailer);\n        }\n\n        [Fact]\n        public void Test__KeyValueAndTrailer()\n        {\n            Tag tag = new Tag(\"key\", \"value\", \"trailer\");\n\n            Assert.Equal(\"key\", tag.key);\n            Assert.Equal(\"value\", tag.value);\n            Assert.Equal(\"trailer\", tag.trailer);\n        }\n\n        [Fact]\n        public void Test__KeyEmptyValueAndTrailer()\n        {\n            Tag tag = new Tag(\"key\", \"\", \"trailer\");\n\n            Assert.Equal(\"key\", tag.key);\n            Assert.Equal(\"\", tag.value);\n            Assert.Equal(\"trailer\", tag.trailer);\n        }\n\n        [Fact]\n        public void TestConstructor__KeyNull()\n        {\n            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(delegate\n            {\n                new Tag(null, \"value\", \"trailer\");\n            });\n\n            Assert.Equal(\"key\", ex.ParamName);\n        }\n\n        [Fact]\n        public void TestConstructor__KeyEmpty()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new Tag(\"\", \"value\", \"trailer\");\n            });\n\n            Assert.Equal(\"key\", ex.ParamName);\n            Assert.Contains(\"can't be empty\", ex.Message);\n        }\n\n        [Fact]\n        public void TestConstructor__ValueNullButTrailerNotNull()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new Tag(\"key\", null, \"trailer\");\n            });\n            \n            Assert.Contains(\"trailer must be null if value is null\", ex.Message);\n        }\n\n        [Fact]\n        public void TestConstructor__TrailerEmpty()\n        {\n            ArgumentException ex = Assert.Throws<ArgumentException>(delegate\n            {\n                new Tag(\"key\", \"value\", \"\");\n            });\n\n            Assert.Equal(\"trailer\", ex.ParamName);\n            Assert.Contains(\"can't be empty (null allowed)\", ex.Message);\n        }\n\n        [Fact]\n        public void TestToString__Key()\n        {\n            Tag tag = new Tag(\"key\", null, null);\n\n            Assert.Equal(\"< 'key' >\", tag.ToString());\n        }\n\n        [Fact]\n        public void TestToString__KeyAndValue()\n        {\n            Tag tag = new Tag(\"key\", \"value\", null);\n\n            Assert.Equal(\"< 'key' [ 'value' ] >\", tag.ToString());\n        }\n\n        [Fact]\n        public void TestToString__KeyAndEmptyValue()\n        {\n            Tag tag = new Tag(\"key\", \"\", null);\n\n            Assert.Equal(\"< 'key' [ '' ] >\", tag.ToString());\n        }\n\n        [Fact]\n        public void TestToString__KeyValueAndTrailer()\n        {\n            Tag tag = new Tag(\"key\", \"value\", \"trailer\");\n\n            Assert.Equal(\"< 'key' [ 'value' ] 'trailer' >\", tag.ToString());\n        }\n\n        [Fact]\n        public void TestToString__KeyEmptyValueAndTrailer()\n        {\n            Tag tag = new Tag(\"key\", \"\", \"trailer\");\n\n            Assert.Equal(\"< 'key' [ '' ] 'trailer' >\", tag.ToString());\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Threading/BackgroundTaskTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing ModuleManager.Threading;\n\nnamespace ModuleManagerTests.Threading\n{\n    public class BackgroundTaskTest\n    {\n        [Fact]\n        public void Test__Start()\n        {\n            bool finish = false;\n            void Run()\n            {\n                while (!finish) continue;\n            }\n\n            ITaskStatus status = BackgroundTask.Start(Run);\n\n            Assert.True(status.IsRunning);\n            Assert.False(status.IsFinished);\n            Assert.False(status.IsExitedWithError);\n            Assert.Null(status.Exception);\n\n            finish = true;\n\n            while (status.IsRunning) continue;\n\n            Assert.False(status.IsRunning);\n            Assert.True(status.IsFinished);\n            Assert.False(status.IsExitedWithError);\n            Assert.Null(status.Exception);\n        }\n\n        [Fact]\n        public void Test__Start__Exception()\n        {\n            bool finish = false;\n            Exception ex = new Exception();\n            void Run()\n            {\n                while (!finish) continue;\n                throw ex;\n            }\n\n            ITaskStatus status = BackgroundTask.Start(Run);\n\n            Assert.True(status.IsRunning);\n            Assert.False(status.IsFinished);\n            Assert.False(status.IsExitedWithError);\n            Assert.Null(status.Exception);\n\n            finish = true;\n\n            while (status.IsRunning) continue;\n\n            Assert.False(status.IsRunning);\n            Assert.False(status.IsFinished);\n            Assert.True(status.IsExitedWithError);\n            Assert.Same(ex, status.Exception);\n        }\n\n        [Fact]\n        public void Test__Run__ArgumentNull()\n        {\n            Assert.Throws<ArgumentNullException>(delegate\n            {\n                BackgroundTask.Start(null);\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Threading/TaskStatusTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing ModuleManager.Threading;\n\nnamespace ModuleManagerTests.Threading\n{\n    public class TaskStatusTest\n    {\n        [Fact]\n        public void Test__Cosntructor()\n        {\n            TaskStatus status = new TaskStatus();\n\n            Assert.True(status.IsRunning);\n            Assert.False(status.IsFinished);\n            Assert.False(status.IsExitedWithError);\n            Assert.Null(status.Exception);\n        }\n\n        [Fact]\n        public void TestFinished()\n        {\n            TaskStatus status = new TaskStatus();\n\n            status.Finished();\n\n            Assert.False(status.IsRunning);\n            Assert.True(status.IsFinished);\n            Assert.False(status.IsExitedWithError);\n            Assert.Null(status.Exception);\n        }\n\n        [Fact]\n        public void TestError()\n        {\n            TaskStatus status = new TaskStatus();\n            Exception ex = new Exception();\n\n            status.Error(ex);\n\n            Assert.False(status.IsRunning);\n            Assert.False(status.IsFinished);\n            Assert.True(status.IsExitedWithError);\n            Assert.Same(ex, status.Exception);\n        }\n\n        [Fact]\n        public void TestFinished__AlreadyFinished()\n        {\n            TaskStatus status = new TaskStatus();\n\n            status.Finished();\n\n            Assert.Throws<InvalidOperationException>(delegate\n            {\n                status.Finished();\n            });\n\n            Assert.False(status.IsRunning);\n            Assert.True(status.IsFinished);\n            Assert.False(status.IsExitedWithError);\n            Assert.Null(status.Exception);\n        }\n\n        [Fact]\n        public void TestFinished__AlreadyErrored()\n        {\n            TaskStatus status = new TaskStatus();\n            Exception ex = new Exception();\n\n            status.Error(ex);\n\n            Assert.Throws<InvalidOperationException>(delegate\n            {\n                status.Finished();\n            });\n\n            Assert.False(status.IsRunning);\n            Assert.False(status.IsFinished);\n            Assert.True(status.IsExitedWithError);\n            Assert.Same(ex, status.Exception);\n        }\n\n        [Fact]\n        public void TestError__AlreadyFinished()\n        {\n            TaskStatus status = new TaskStatus();\n\n            status.Finished();\n\n            Assert.Throws<InvalidOperationException>(delegate\n            {\n                status.Error(new Exception());\n            });\n\n            Assert.False(status.IsRunning);\n            Assert.True(status.IsFinished);\n            Assert.False(status.IsExitedWithError);\n            Assert.Null(status.Exception);\n        }\n\n        [Fact]\n        public void TestError__AlreadyErrored()\n        {\n            TaskStatus status = new TaskStatus();\n            Exception ex = new Exception();\n\n            status.Error(ex);\n\n            Assert.Throws<InvalidOperationException>(delegate\n            {\n                status.Error(new Exception());\n            });\n\n            Assert.False(status.IsRunning);\n            Assert.False(status.IsFinished);\n            Assert.True(status.IsExitedWithError);\n            Assert.Same(ex, status.Exception);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/Utils/CounterTest.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Xunit;\nusing ModuleManager.Utils;\n\nnamespace ModuleManagerTests.Utils\n{\n    public class CounterTest\n    {\n        [Fact]\n        public void Test__Constructor()\n        {\n            Counter counter = new Counter();\n\n            Assert.Equal(0, counter.Value);\n        }\n\n        [Fact]\n        public void TestIncrement()\n        {\n            Counter counter = new Counter();\n\n            Assert.Equal(0, counter.Value);\n\n            counter.Increment();\n\n            Assert.Equal(1, counter.Value);\n\n            counter.Increment();\n\n            Assert.Equal(2, counter.Value);\n        }\n\n        [Fact]\n        public void TestToString()\n        {\n            Counter counter = new Counter();\n            \n            Assert.Equal(\"0\", counter.ToString());\n\n            counter.Increment();\n            \n            Assert.Equal(\"1\", counter.ToString());\n\n            counter.Increment();\n            \n            Assert.Equal(\"2\", counter.ToString());\n        }\n\n        [Fact]\n        public void Test__CastAsInt()\n        {\n            Counter counter = new Counter();\n            int i;\n\n            i = counter;\n            Assert.Equal(0, i);\n\n            counter.Increment();\n\n            i = counter;\n            Assert.Equal(1, i);\n\n            counter.Increment();\n\n            i = counter;\n            Assert.Equal(2, i);\n        }\n    }\n}\n"
  },
  {
    "path": "ModuleManagerTests/app.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n  <runtime>\n    <assemblyBinding xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n      <dependentAssembly>\n        <assemblyIdentity name=\"System.Threading.Tasks.Extensions\" publicKeyToken=\"cc7b13ffcd2ddd51\" culture=\"neutral\" />\n        <bindingRedirect oldVersion=\"0.0.0.0-4.2.0.1\" newVersion=\"4.2.0.1\" />\n      </dependentAssembly>\n      <dependentAssembly>\n        <assemblyIdentity name=\"System.Runtime.CompilerServices.Unsafe\" publicKeyToken=\"b03f5f7f11d50a3a\" culture=\"neutral\" />\n        <bindingRedirect oldVersion=\"0.0.0.0-5.0.0.0\" newVersion=\"5.0.0.0\" />\n      </dependentAssembly>\n    </assemblyBinding>\n  </runtime>\n</configuration>"
  },
  {
    "path": "ModuleManagerTests/packages.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"Castle.Core\" version=\"4.4.1\" targetFramework=\"net471\" />\n  <package id=\"NSubstitute\" version=\"4.2.2\" targetFramework=\"net471\" />\n  <package id=\"System.Runtime.CompilerServices.Unsafe\" version=\"5.0.0\" targetFramework=\"net471\" />\n  <package id=\"System.Threading.Tasks.Extensions\" version=\"4.5.4\" targetFramework=\"net471\" />\n  <package id=\"xunit\" version=\"2.4.1\" targetFramework=\"net471\" />\n  <package id=\"xunit.abstractions\" version=\"2.0.3\" targetFramework=\"net471\" />\n  <package id=\"xunit.analyzers\" version=\"0.10.0\" targetFramework=\"net471\" />\n  <package id=\"xunit.assert\" version=\"2.4.1\" targetFramework=\"net471\" />\n  <package id=\"xunit.core\" version=\"2.4.1\" targetFramework=\"net471\" />\n  <package id=\"xunit.extensibility.core\" version=\"2.4.1\" targetFramework=\"net471\" />\n  <package id=\"xunit.extensibility.execution\" version=\"2.4.1\" targetFramework=\"net471\" />\n  <package id=\"xunit.runner.console\" version=\"2.4.1\" targetFramework=\"net471\" developmentDependency=\"true\" />\n  <package id=\"xunit.runner.visualstudio\" version=\"2.4.3\" targetFramework=\"net471\" developmentDependency=\"true\" />\n</packages>"
  },
  {
    "path": "README.md",
    "content": "ModuleManager\n=============\n\n\nOriginal (c) from Ialdabaoth ( https://github.com/Ialdabaoth )\n\nModified by // Modifications by // Maintained by sarbian ( https://github.com/sarbian )\n\n\nThe original licence requirement was:\n\n---\n\nunder a CC share-alike license. Anyone is free to do anything they like with ModuleManager's source, with two caveats:\n\n1. You credit me as the original creator that your code is based on\n2. You make it ABSOLUTELY CLEAR that your code is not the original ModuleManager, and that any problems that people have with your fork should be taken up with YOU, not me.\n\n---\n\n\nTHIS IS NOT THE ORIGINAL MODULEMANAGER CODE.\n\nDo not bother Ialdabaoth about any problems with it.\n\n## Dependencies\n\n- mono resgen2\n  - Fedora: `sudo dnf install mono-devel`\n- Mono C# Compiler\n  - Fedora: `sudo ln -s /usr/bin/mcs /usr/bin/gmcs`\n\n"
  },
  {
    "path": "TestUtils/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled through the following\n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyTitle(\"TestUtils\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"TestUtils\")]\n[assembly: AssemblyCopyright(\"Copyright ©  2017\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible\n// to COM components.  If you need to access a type in this assembly from\n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n// The following GUID is for the ID of the typelib if this project is exposed to COM\n[assembly: Guid(\"20eaafe6-510d-4374-8d2f-6b52d0178e85\")]\n\n// Version information for an assembly consists of the following four values:\n//\n//      Major Version\n//      Minor Version\n//      Build Number\n//      Revision\n//\n// You can specify all the values or you can default the Build and Revision Numbers\n// by using the '*' as shown below:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"1.0.0.0\")]\n[assembly: AssemblyFileVersion(\"1.0.0.0\")]\n"
  },
  {
    "path": "TestUtils/TestConfigNode.cs",
    "content": "﻿using System;\nusing System.Collections;\n\nnamespace TestUtils\n{\n    public class TestConfigNode : ConfigNode, IEnumerable\n    {\n        public TestConfigNode() : base() { }\n        public TestConfigNode(string name) : base(name) { }\n\n        public void Add(string name, string value) => Add(new Value(name, value));\n        public void Add(Value value) => values.Add(value);\n        public void Add(string name, ConfigNode node) => AddNode(name, node);\n        public void Add(ConfigNode node) => AddNode(node);\n\n        public IEnumerator GetEnumerator() => throw new NotImplementedException();\n    }\n}\n"
  },
  {
    "path": "TestUtils/TestUtils.csproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" />\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n    <ProjectGuid>{20EAAFE6-510D-4374-8D2F-6B52D0178E85}</ProjectGuid>\n    <OutputType>Library</OutputType>\n    <AppDesignerFolder>Properties</AppDesignerFolder>\n    <RootNamespace>TestUtils</RootNamespace>\n    <AssemblyName>TestUtils</AssemblyName>\n    <TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>\n    <FileAlignment>512</FileAlignment>\n    <TargetFrameworkProfile />\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    <DebugSymbols>true</DebugSymbols>\n    <DebugType>full</DebugType>\n    <Optimize>false</Optimize>\n    <OutputPath>bin\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n    <Prefer32Bit>false</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    <DebugType>pdbonly</DebugType>\n    <Optimize>true</Optimize>\n    <OutputPath>bin\\Release\\</OutputPath>\n    <DefineConstants>TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n    <Prefer32Bit>false</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup>\n    <LangVersion>8.0</LangVersion>\n  </PropertyGroup>\n  <ItemGroup>\n    <Reference Include=\"Assembly-CSharp\" />\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Xml.Linq\" />\n    <Reference Include=\"System.Data.DataSetExtensions\" />\n    <Reference Include=\"System.Data\" />\n    <Reference Include=\"System.Xml\" />\n    <Reference Include=\"UnityEngine\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"Properties\\AssemblyInfo.cs\" />\n    <Compile Include=\"TestConfigNode.cs\" />\n    <Compile Include=\"UrlBuilder.cs\" />\n  </ItemGroup>\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n</Project>"
  },
  {
    "path": "TestUtils/UrlBuilder.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Linq;\nusing System.Reflection;\n\nnamespace TestUtils\n{\n    public static class UrlBuilder\n    {\n        private static readonly FieldInfo UrlDir__field__type;\n        private static readonly FieldInfo UrlDir__field__name;\n        private static readonly FieldInfo UrlDir__field__root;\n        private static readonly FieldInfo UrlDir__field__parent;\n\n        private static readonly FieldInfo UrlFile__field__name;\n        private static readonly FieldInfo UrlFile__field__fileType;\n        private static readonly FieldInfo UrlFile__field__fileExtension;\n\n        static UrlBuilder()\n        {\n            FieldInfo[] UrlDirFields = typeof(UrlDir).GetFields(BindingFlags.Instance | BindingFlags.NonPublic);\n            FieldInfo[] UrlDirFields__DirectoryType = UrlDirFields.Where(field => field.FieldType == typeof(UrlDir.DirectoryType)).ToArray();\n            FieldInfo[] UrlDirFields__string = UrlDirFields.Where(field => field.FieldType == typeof(string)).ToArray();\n            FieldInfo[] UrlDirFields__UrlDir = UrlDirFields.Where(field => field.FieldType == typeof(UrlDir)).ToArray();\n\n            UrlDir__field__type = UrlDirFields__DirectoryType[0];\n            UrlDir__field__name = UrlDirFields__string[0];\n            UrlDir__field__root = UrlDirFields__UrlDir[0];\n            UrlDir__field__parent = UrlDirFields__UrlDir[1];\n\n            FieldInfo[] UrlFileFields = typeof(UrlDir.UrlFile).GetFields(BindingFlags.Instance | BindingFlags.NonPublic);\n            FieldInfo[] UrlFileFields__string = UrlFileFields.Where(field => field.FieldType == typeof(string)).ToArray();\n            FieldInfo[] UrlFileFields__FileType = UrlFileFields.Where(field => field.FieldType == typeof(UrlDir.FileType)).ToArray();\n\n            UrlFile__field__name = UrlFileFields__string[0];\n            UrlFile__field__fileExtension = UrlFileFields__string[2];\n            UrlFile__field__fileType = UrlFileFields__FileType[0];\n        }\n\n        public static UrlDir CreateRoot()\n        {\n            return new UrlDir(new UrlDir.ConfigDirectory[0], new UrlDir.ConfigFileType[0]);\n        }\n\n        public static UrlDir CreateDir(string url, UrlDir parent = null)\n        {\n            if (parent == null)\n            {\n                parent = CreateRoot();\n            }\n            else\n            {\n                UrlDir existingDir = parent.GetDirectory(url);\n                if (existingDir != null) return existingDir;\n            }\n\n            UrlDir current = parent;\n\n            foreach(string name in url.Split('/'))\n            {\n                UrlDir dir = CreateRoot();\n                UrlDir__field__name.SetValue(dir, name);\n                UrlDir__field__root.SetValue(dir, current.root);\n                UrlDir__field__parent.SetValue(dir, current);\n\n                current.children.Add(dir);\n                current = dir;\n            }\n\n            return current;\n        }\n\n        public static UrlDir CreateGameData(UrlDir root = null)\n        {\n            if (root == null)\n            {\n                root = CreateRoot();\n            }\n            else\n            {\n                UrlDir potentialGameData = root.children.FirstOrDefault(dir => dir.type == UrlDir.DirectoryType.GameData && dir.name == \"\");\n                if (potentialGameData != null) return potentialGameData;\n            }\n\n            UrlDir gameData = CreateRoot();\n            UrlDir__field__name.SetValue(gameData, \"\");\n            UrlDir__field__type.SetValue(gameData, UrlDir.DirectoryType.GameData);\n            UrlDir__field__root.SetValue(gameData, root);\n            UrlDir__field__parent.SetValue(gameData, root);\n\n            root.children.Add(gameData);\n\n            return gameData;\n        }\n\n        public static UrlDir.UrlFile CreateFile(string path, UrlDir parent = null)\n        {\n            int sepIndex = path.LastIndexOf('/');\n            string name = path;\n\n            if (sepIndex != -1)\n            {\n                parent = CreateDir(path.Substring(0, sepIndex), parent);\n                name = path.Substring(sepIndex + 1);\n            }\n            else if (parent == null)\n            {\n                parent = CreateRoot();\n            }\n\n            string nameWithoutExtension = Path.GetFileNameWithoutExtension(name);\n            string extension = Path.GetExtension(name);\n            if (!string.IsNullOrEmpty(extension)) extension = extension.Substring(1);\n\n            UrlDir.UrlFile existingFile = parent.files.FirstOrDefault(f => f.name == nameWithoutExtension && f.fileExtension == extension);\n            if (existingFile != null) return existingFile;\n\n            bool cfg = false;\n            string newName = name;\n\n            UrlDir.FileType fileType = UrlDir.FileType.Unknown;\n\n            switch (extension)\n            {\n                case \"dll\":\n                    fileType = UrlDir.FileType.Assembly;\n                    break;\n                case \"ksp\":\n                    fileType = UrlDir.FileType.AssetBundle;\n                    break;\n                case \"wav\":\n                case \"ogg\":\n                    fileType = UrlDir.FileType.Audio;\n                    break;\n                case \"cfg\":\n                    fileType = UrlDir.FileType.Config;\n                    break;\n                case \"dae\":\n                case \"mu\":\n                    fileType = UrlDir.FileType.Model;\n                    break;\n                case \"dds\":\n                case \"jpg\":\n                case \"jpeg\":\n                case \"mbm\":\n                case \"png\":\n                case \"tga\":\n                case \"truecolor\":\n                    fileType = UrlDir.FileType.Texture;\n                    break;\n            }\n\n            // KSP tries to load .cfg files so need to have special handling\n            if (extension == \"cfg\")\n            {\n                cfg = true;\n                newName = name + \".not_cfg\";\n            }\n\n            UrlDir.UrlFile file = new UrlDir.UrlFile(parent, new FileInfo(newName));\n\n            UrlFile__field__fileType.SetValue(file, fileType);\n\n            if (cfg)\n            {\n                UrlFile__field__name.SetValue(file, nameWithoutExtension);\n                UrlFile__field__fileExtension.SetValue(file, \"cfg\");\n            }\n\n            parent.files.Add(file);\n\n            return file;\n        }\n\n        public static UrlDir.UrlConfig CreateConfig(ConfigNode node, UrlDir.UrlFile parent)\n        {\n            UrlDir.UrlConfig config = new UrlDir.UrlConfig(parent, node);\n            parent.configs.Add(config);\n            return config;\n        }\n\n        public static UrlDir.UrlConfig CreateConfig(string url, ConfigNode node, UrlDir parent = null)\n        {\n            if (Path.GetExtension(url) != \".cfg\") url += \".cfg\";\n\n            UrlDir.UrlFile file = CreateFile(url, parent);\n\n            return CreateConfig(node, file);\n        }\n    }\n}\n"
  },
  {
    "path": "TestUtilsTests/DummyTest.cs",
    "content": "﻿using System;\nusing Xunit;\n\nnamespace TestUtilsTests\n{\n    public class DummyTest\n    {\n        [Fact]\n        public void PassingTest()\n        {\n            Assert.True(true);\n        }\n    }\n}\n"
  },
  {
    "path": "TestUtilsTests/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled through the following\n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyTitle(\"TestUtilsTests\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"TestUtilsTests\")]\n[assembly: AssemblyCopyright(\"Copyright ©  2017\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible\n// to COM components.  If you need to access a type in this assembly from\n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n// The following GUID is for the ID of the typelib if this project is exposed to COM\n[assembly: Guid(\"e695c11f-4217-4014-9b51-7232a654c205\")]\n\n// Version information for an assembly consists of the following four values:\n//\n//      Major Version\n//      Minor Version\n//      Build Number\n//      Revision\n//\n// You can specify all the values or you can default the Build and Revision Numbers\n// by using the '*' as shown below:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"1.0.0.0\")]\n[assembly: AssemblyFileVersion(\"1.0.0.0\")]\n"
  },
  {
    "path": "TestUtilsTests/TestConfigNodeTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing TestUtils;\n\nnamespace TestUtilsTests\n{\n    public class TestConfigNodeTest\n    {\n        [Fact]\n        public void TestTestConfigNode()\n        {\n            ConfigNode node = new TestConfigNode(\"NODE\")\n            {\n                { \"value1\", \"something\" },\n                { \"value2\", \"something else\" },\n                { \"multiple\", \"first\" },\n                { \"multiple\", \"second\" },\n                new ConfigNode.Value(\"foo\", \"bar\"),\n                { \"weird_values\", \"some\\r\\n\\tstuff\" },\n                { \"NODE_1\", new TestConfigNode\n                    {\n                        { \"name\", \"something\" },\n                        { \"stuff\", \"something else\" },\n                    }\n                },\n                new TestConfigNode(\"MULTIPLE\")\n                {\n                    { \"value3\", \"blah\" },\n                    { \"value4\", \"bleh\" },\n                },\n                new TestConfigNode(\"MULTIPLE\")\n                {\n                    { \"value3\", \"blih\" },\n                    { \"value4\", \"bloh\" },\n                },\n            };\n\n            Assert.Equal(6, node.values.Count);\n            AssertValue(\"value1\", \"something\", node.values[0]);\n            AssertValue(\"value2\", \"something else\", node.values[1]);\n            AssertValue(\"multiple\", \"first\", node.values[2]);\n            AssertValue(\"multiple\", \"second\", node.values[3]);\n            AssertValue(\"foo\", \"bar\", node.values[4]);\n            AssertValue(\"weird_values\", \"some\\r\\n\\tstuff\", node.values[5]);\n\n            Assert.Equal(3, node.nodes.Count);\n            ConfigNode innerNode1 = node.GetNode(\"NODE_1\");\n            Assert.NotNull(innerNode1);\n\n            Assert.Equal(\"NODE_1\", node.nodes[0].name);\n            Assert.Equal(2, node.nodes[0].values.Count);\n            AssertValue(\"name\", \"something\", node.nodes[0].values[0]);\n            AssertValue(\"stuff\", \"something else\", node.nodes[0].values[1]);\n            Assert.Empty(node.nodes[0].nodes);\n\n            Assert.Equal(\"MULTIPLE\", node.nodes[1].name);\n            Assert.Equal(2, node.nodes[1].values.Count);\n            AssertValue(\"value3\", \"blah\", node.nodes[1].values[0]);\n            AssertValue(\"value4\", \"bleh\", node.nodes[1].values[1]);\n            Assert.Empty(node.nodes[1].nodes);\n\n            Assert.Equal(\"MULTIPLE\", node.nodes[2].name);\n            Assert.Equal(2, node.nodes[2].values.Count);\n            AssertValue(\"value3\", \"blih\", node.nodes[2].values[0]);\n            AssertValue(\"value4\", \"bloh\", node.nodes[2].values[1]);\n            Assert.Empty(node.nodes[2].nodes);\n        }\n\n        private void AssertValue(string name, string value, ConfigNode.Value nodeValue)\n        {\n            Assert.Equal(name, nodeValue.name);\n            Assert.Equal(value, nodeValue.value);\n        }\n    }\n}\n"
  },
  {
    "path": "TestUtilsTests/TestUtilsTests.csproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Import Project=\"..\\packages\\xunit.runner.visualstudio.2.4.3\\build\\net452\\xunit.runner.visualstudio.props\" Condition=\"Exists('..\\packages\\xunit.runner.visualstudio.2.4.3\\build\\net452\\xunit.runner.visualstudio.props')\" />\n  <Import Project=\"..\\packages\\xunit.runner.console.2.4.1\\build\\xunit.runner.console.props\" Condition=\"Exists('..\\packages\\xunit.runner.console.2.4.1\\build\\xunit.runner.console.props')\" />\n  <Import Project=\"..\\packages\\xunit.core.2.4.1\\build\\xunit.core.props\" Condition=\"Exists('..\\packages\\xunit.core.2.4.1\\build\\xunit.core.props')\" />\n  <Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" />\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n    <ProjectGuid>{E695C11F-4217-4014-9B51-7232A654C205}</ProjectGuid>\n    <OutputType>Library</OutputType>\n    <AppDesignerFolder>Properties</AppDesignerFolder>\n    <RootNamespace>TestUtilsTests</RootNamespace>\n    <AssemblyName>TestUtilsTests</AssemblyName>\n    <TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>\n    <FileAlignment>512</FileAlignment>\n    <NuGetPackageImportStamp>\n    </NuGetPackageImportStamp>\n    <TargetFrameworkProfile />\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    <DebugSymbols>true</DebugSymbols>\n    <DebugType>full</DebugType>\n    <Optimize>false</Optimize>\n    <OutputPath>bin\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n    <Prefer32Bit>false</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    <DebugType>pdbonly</DebugType>\n    <Optimize>true</Optimize>\n    <OutputPath>bin\\Release\\</OutputPath>\n    <DefineConstants>TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n    <Prefer32Bit>false</Prefer32Bit>\n  </PropertyGroup>\n  <PropertyGroup>\n    <LangVersion>8.0</LangVersion>\n  </PropertyGroup>\n  <ItemGroup>\n    <Reference Include=\"Assembly-CSharp\" />\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Xml.Linq\" />\n    <Reference Include=\"System.Data.DataSetExtensions\" />\n    <Reference Include=\"System.Data\" />\n    <Reference Include=\"System.Xml\" />\n    <Reference Include=\"UnityEngine\" />\n    <Reference Include=\"xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\xunit.abstractions.2.0.3\\lib\\net35\\xunit.abstractions.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"xunit.assert, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\xunit.assert.2.4.1\\lib\\netstandard1.1\\xunit.assert.dll</HintPath>\n    </Reference>\n    <Reference Include=\"xunit.core, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\xunit.extensibility.core.2.4.1\\lib\\net452\\xunit.core.dll</HintPath>\n    </Reference>\n    <Reference Include=\"xunit.execution.desktop, Version=2.4.1.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\xunit.extensibility.execution.2.4.1\\lib\\net452\\xunit.execution.desktop.dll</HintPath>\n    </Reference>\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"DummyTest.cs\" />\n    <Compile Include=\"Properties\\AssemblyInfo.cs\" />\n    <Compile Include=\"TestConfigNodeTest.cs\" />\n    <Compile Include=\"UrlBuilderTest.cs\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"packages.config\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\TestUtils\\TestUtils.csproj\">\n      <Project>{20eaafe6-510d-4374-8d2f-6b52d0178e85}</Project>\n      <Name>TestUtils</Name>\n    </ProjectReference>\n  </ItemGroup>\n  <ItemGroup>\n    <Service Include=\"{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Analyzer Include=\"..\\packages\\xunit.analyzers.0.10.0\\analyzers\\dotnet\\cs\\xunit.analyzers.dll\" />\n  </ItemGroup>\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n  <Target Name=\"EnsureNuGetPackageBuildImports\" BeforeTargets=\"PrepareForBuild\">\n    <PropertyGroup>\n      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>\n    </PropertyGroup>\n    <Error Condition=\"!Exists('..\\packages\\xunit.core.2.4.1\\build\\xunit.core.props')\" Text=\"$([System.String]::Format('$(ErrorText)', '..\\packages\\xunit.core.2.4.1\\build\\xunit.core.props'))\" />\n    <Error Condition=\"!Exists('..\\packages\\xunit.core.2.4.1\\build\\xunit.core.targets')\" Text=\"$([System.String]::Format('$(ErrorText)', '..\\packages\\xunit.core.2.4.1\\build\\xunit.core.targets'))\" />\n    <Error Condition=\"!Exists('..\\packages\\xunit.runner.console.2.4.1\\build\\xunit.runner.console.props')\" Text=\"$([System.String]::Format('$(ErrorText)', '..\\packages\\xunit.runner.console.2.4.1\\build\\xunit.runner.console.props'))\" />\n    <Error Condition=\"!Exists('..\\packages\\xunit.runner.visualstudio.2.4.3\\build\\net452\\xunit.runner.visualstudio.props')\" Text=\"$([System.String]::Format('$(ErrorText)', '..\\packages\\xunit.runner.visualstudio.2.4.3\\build\\net452\\xunit.runner.visualstudio.props'))\" />\n  </Target>\n  <Import Project=\"..\\packages\\xunit.core.2.4.1\\build\\xunit.core.targets\" Condition=\"Exists('..\\packages\\xunit.core.2.4.1\\build\\xunit.core.targets')\" />\n</Project>"
  },
  {
    "path": "TestUtilsTests/UrlBuilderTest.cs",
    "content": "﻿using System;\nusing Xunit;\nusing TestUtils;\n\nnamespace TestUtilsTests\n{\n    public class UrlBuilderTest\n    {\n        [Fact]\n        public void TestCreateRoot()\n        {\n            UrlDir root = UrlBuilder.CreateRoot();\n\n            Assert.Equal(\"root\", root.name);\n            Assert.Null(root.parent);\n            Assert.Same(root, root.root);\n        }\n\n        [Fact]\n        public void TestCreateDir()\n        {\n            UrlDir dir = UrlBuilder.CreateDir(\"abc\");\n\n            Assert.Equal(\"abc\", dir.name);\n\n            UrlDir root = dir.parent;\n            Assert.NotNull(root);\n            Assert.Equal(\"root\", root.name);\n            Assert.Null(root.parent);\n            Assert.Contains(dir, root.children);\n\n            Assert.Same(root, dir.root);\n            Assert.Same(root, root.root);\n        }\n\n        [Fact]\n        public void TestCreateDir__Parent()\n        {\n            UrlDir root = UrlBuilder.CreateRoot();\n\n            UrlDir child1 = UrlBuilder.CreateDir(\"child1\", root);\n\n            Assert.Equal(\"child1\", child1.name);\n            Assert.Same(root, child1.parent);\n            Assert.Same(root, child1.root);\n\n            Assert.Equal(\"child1\", child1.url);\n\n            Assert.Contains(child1, root.children);\n\n            UrlDir child2 = UrlBuilder.CreateDir(\"child2\", child1);\n\n            Assert.Equal(\"child2\", child2.name);\n            Assert.Same(child1, child2.parent);\n            Assert.Same(root, child2.root);\n\n            Assert.Equal(\"child1/child2\", child2.url);\n\n            Assert.Contains(child2, child1.children);\n        }\n\n        [Fact]\n        public void TestCreateDir__Url()\n        {\n            UrlDir dir = UrlBuilder.CreateDir(\"abc/def\");\n\n            Assert.Equal(\"def\", dir.name);\n\n            UrlDir parent = dir.parent;\n\n            Assert.NotNull(parent);\n            Assert.Equal(\"abc\", parent.name);\n            Assert.Contains(dir, parent.children);\n\n            UrlDir root = parent.parent;\n\n            Assert.NotNull(root);\n            Assert.Equal(\"root\", root.name);\n            Assert.Contains(parent, root.children);\n            Assert.Null(root.parent);\n\n            Assert.Same(root, root.root);\n            Assert.Same(root, parent.root);\n            Assert.Same(root, dir.root);\n\n            Assert.Contains(dir, root.AllDirectories);\n        }\n\n        [Fact]\n        public void TestCreateDir__Url__Parent()\n        {\n            UrlDir root = UrlBuilder.CreateRoot();\n            UrlDir parent1 = UrlBuilder.CreateDir(\"abc\", root);\n\n            UrlDir dir = UrlBuilder.CreateDir(\"def/ghi\", parent1);\n\n            Assert.Equal(\"ghi\", dir.name);\n\n            UrlDir parent2 = dir.parent;\n\n            Assert.NotNull(parent2);\n            Assert.Equal(\"def\", parent2.name);\n            Assert.Contains(dir, parent2.children);\n\n            Assert.Same(parent1, parent2.parent);\n            Assert.Contains(parent2, parent1.children);\n\n            Assert.Same(root, dir.root);\n            Assert.Same(root, parent2.root);\n\n            Assert.Contains(dir, root.AllDirectories);\n            Assert.Contains(dir, parent1.AllDirectories);\n        }\n\n        [Fact]\n        public void TestCreateDir__Url__AlreadyExists()\n        {\n            UrlDir root = UrlBuilder.CreateRoot();\n\n            UrlDir dir1 = UrlBuilder.CreateDir(\"abc/def\", root);\n            UrlDir dir2 = UrlBuilder.CreateDir(\"abc/def\", root);\n\n            Assert.Same(dir1, dir2);\n\n            Assert.Equal(\"def\", dir1.name);\n\n            UrlDir parent = dir1.parent;\n\n            Assert.NotNull(parent);\n            Assert.Equal(\"abc\", parent.name);\n            Assert.Contains(dir1, parent.children);\n\n            Assert.Same(root, dir1.root);\n            Assert.Same(root, parent.root);\n            Assert.Contains(dir1, root.AllDirectories);\n        }\n\n        [Fact]\n        public void TestCreateGameData()\n        {\n            UrlDir gameData = UrlBuilder.CreateGameData();\n\n            Assert.Equal(\"\", gameData.name);\n            Assert.Equal(\"\", gameData.url);\n            Assert.Equal(UrlDir.DirectoryType.GameData, gameData.type);\n            UrlDir root = Assert.IsType<UrlDir>(gameData.root);\n            Assert.Same(root, gameData.parent);\n            Assert.Equal(\"root\", root.name);\n            Assert.Null(root.parent);\n            Assert.Same(root, root.root);\n            Assert.Contains(gameData, root.children);\n        }\n\n        [Fact]\n        public void TestCreateGameData__SpecifyRoot()\n        {\n            UrlDir root = UrlBuilder.CreateRoot();\n            UrlDir gameData = UrlBuilder.CreateGameData(root);\n\n            Assert.Equal(\"\", gameData.name);\n            Assert.Equal(\"\", gameData.url);\n            Assert.Equal(UrlDir.DirectoryType.GameData, gameData.type);\n            Assert.Same(root, gameData.parent);\n            Assert.Contains(gameData, root.children);\n        }\n\n        [Fact]\n        public void TestCreateGameData__SpecifyRoot__GameDataAlreadyExists()\n        {\n            UrlDir root = UrlBuilder.CreateRoot();\n            UrlDir gameData1 = UrlBuilder.CreateGameData(root);\n            UrlDir gameData2 = UrlBuilder.CreateGameData(root);\n\n            Assert.Same(gameData1, gameData2);\n        }\n\n        [Fact]\n        public void TestCreateFile()\n        {\n            UrlDir.UrlFile file = UrlBuilder.CreateFile(\"someFile.txt\");\n\n            Assert.Equal(\"someFile\", file.name);\n            Assert.Equal(\"txt\", file.fileExtension);\n            Assert.Equal(UrlDir.FileType.Unknown, file.fileType);\n\n            UrlDir root = file.parent;\n            Assert.NotNull(root);\n            Assert.Equal(\"root\", root.name);\n            Assert.Null(root.parent);\n            Assert.Contains(file, root.files);\n            Assert.Same(root, file.root);\n        }\n\n        [Fact]\n        public void TestCreateFile__Parent()\n        {\n            UrlDir root = UrlBuilder.CreateRoot();\n            UrlDir dir = UrlBuilder.CreateDir(\"someDir\", root);\n            UrlDir.UrlFile file = UrlBuilder.CreateFile(\"someFile.txt\", dir);\n\n            Assert.Equal(\"someFile\", file.name);\n            Assert.Equal(\"txt\", file.fileExtension);\n            Assert.Equal(UrlDir.FileType.Unknown, file.fileType);\n            Assert.Same(dir, file.parent);\n            Assert.Same(root, file.root);\n\n            Assert.Equal(\"someDir/someFile\", file.url);\n            Assert.Contains(file, dir.files);\n            Assert.Contains(file, root.AllFiles);\n        }\n\n        [InlineData(\"dll\", UrlDir.FileType.Assembly)]\n        [InlineData(\"ksp\", UrlDir.FileType.AssetBundle)]\n        [InlineData(\"wav\", UrlDir.FileType.Audio)]\n        [InlineData(\"ogg\", UrlDir.FileType.Audio)]\n        [InlineData(\"cfg\", UrlDir.FileType.Config)]\n        [InlineData(\"dae\", UrlDir.FileType.Model)]\n        [InlineData(\"mu\", UrlDir.FileType.Model)]\n        [InlineData(\"dds\", UrlDir.FileType.Texture)]\n        [InlineData(\"jpg\", UrlDir.FileType.Texture)]\n        [InlineData(\"jpeg\", UrlDir.FileType.Texture)]\n        [InlineData(\"mbm\", UrlDir.FileType.Texture)]\n        [InlineData(\"png\", UrlDir.FileType.Texture)]\n        [InlineData(\"tga\", UrlDir.FileType.Texture)]\n        [InlineData(\"truecolor\", UrlDir.FileType.Texture)]\n        [InlineData(\"txt\", UrlDir.FileType.Unknown)]\n        [InlineData(\"xml\", UrlDir.FileType.Unknown)]\n        [Theory]\n        public void TestCreateFile__Extension(string extension, UrlDir.FileType fileType)\n        {\n            UrlDir.UrlFile file = UrlBuilder.CreateFile(\"someFile.\" + extension);\n\n            Assert.Equal(\"someFile\", file.name);\n            Assert.Equal(extension, file.fileExtension);\n            Assert.Equal(fileType, file.fileType);\n\n            UrlDir root = file.parent;\n            Assert.NotNull(root);\n            Assert.Equal(\"root\", root.name);\n            Assert.Null(root.parent);\n            Assert.Contains(file, root.files);\n            Assert.Same(root, file.root);\n        }\n\n        [Fact]\n        public void TestCreateFile__Url()\n        {\n            UrlDir.UrlFile file = UrlBuilder.CreateFile(\"abc/def/ghi.txt\");\n\n            Assert.Equal(\"ghi\", file.name);\n            Assert.Equal(\"txt\", file.fileExtension);\n            Assert.Equal(UrlDir.FileType.Unknown, file.fileType);\n            Assert.Equal(\"abc/def/ghi\", file.url);\n\n            UrlDir parent1 = file.parent;\n            Assert.NotNull(parent1);\n            Assert.Equal(\"def\", parent1.name);\n            Assert.Contains(file, parent1.files);\n\n            UrlDir parent2 = parent1.parent;\n            Assert.NotNull(parent2);\n            Assert.Equal(\"abc\", parent2.name);\n            Assert.Contains(parent1, parent2.children);\n\n            UrlDir root = parent2.parent;\n            Assert.NotNull(root);\n            Assert.Equal(\"root\", root.name);\n            Assert.Contains(parent2, root.children);\n            Assert.Null(root.parent);\n\n            Assert.Same(root, file.root);\n            Assert.Same(root, parent1.root);\n            Assert.Same(root, parent2.root);\n            Assert.Same(root, root.root);\n\n            Assert.Contains(file, root.AllFiles);\n            Assert.Contains(file, parent2.AllFiles);\n        }\n\n        [Fact]\n        public void TestCreateFile__Url__AlreadyExists()\n        {\n            UrlDir root = UrlBuilder.CreateRoot();\n            UrlDir.UrlFile file1 = UrlBuilder.CreateFile(\"abc/def.txt\", root);\n            UrlDir.UrlFile file2 = UrlBuilder.CreateFile(\"abc/def.txt\", root);\n\n            Assert.Same(file1, file2);\n\n            Assert.Equal(\"def\", file1.name);\n            Assert.Equal(\"txt\", file1.fileExtension);\n            Assert.Equal(UrlDir.FileType.Unknown, file1.fileType);\n            Assert.Equal(\"abc/def\", file1.url);\n\n            UrlDir parent1 = file1.parent;\n            Assert.NotNull(parent1);\n            Assert.Equal(\"abc\", parent1.name);\n            Assert.Contains(file1, parent1.files);\n\n            Assert.Same(root, file1.root);\n            Assert.Same(root, parent1.root);\n\n            Assert.Contains(file1, root.AllFiles);\n        }\n\n        [Fact]\n        public void TestCreateConfig()\n        {\n            ConfigNode node = new TestConfigNode(\"SOME_NODE\")\n            {\n                { \"name\", \"blah\" },\n                { \"foo\", \"bar\" },\n            };\n            UrlDir.UrlFile file = UrlBuilder.CreateFile(\"abc/def.cfg\");\n            UrlDir.UrlConfig config = UrlBuilder.CreateConfig(node, file);\n\n            Assert.Equal(\"SOME_NODE\", config.type);\n            Assert.Equal(\"blah\", config.name);\n            Assert.Same(node, config.config);\n            Assert.Equal(\"abc/def/blah\", config.url); // I don't know why this is correct, but it is\n            Assert.Same(file, config.parent);\n            Assert.Contains(config, file.configs);\n        }\n\n        [Fact]\n        public void TestCreateConfig__Url()\n        {\n            ConfigNode node = new TestConfigNode(\"SOME_NODE\")\n            {\n                { \"name\", \"blah\" },\n                { \"foo\", \"bar\" },\n            };\n            UrlDir.UrlConfig config = UrlBuilder.CreateConfig(\"abc/def\", node);\n\n            Assert.Equal(\"SOME_NODE\", config.type);\n            Assert.Equal(\"blah\", config.name);\n            Assert.Same(node, config.config);\n            Assert.Equal(\"abc/def/blah\", config.url);\n\n            UrlDir.UrlFile file = config.parent;\n            Assert.NotNull(file);\n            Assert.Equal(\"def\", file.name);\n            Assert.Equal(\"cfg\", file.fileExtension);\n            Assert.Equal(UrlDir.FileType.Config, file.fileType);\n            Assert.Contains(config, file.configs);\n\n            UrlDir parent = file.parent;\n            Assert.NotNull(parent);\n            Assert.Equal(\"abc\", parent.name);\n            Assert.Contains(file, parent.files);\n\n            UrlDir root = parent.parent;\n            Assert.NotNull(root);\n            Assert.Equal(\"root\", root.name);\n            Assert.Contains(parent, root.children);\n            Assert.Null(root.parent);\n\n            Assert.Same(root, file.root);\n            Assert.Same(root, parent.root);\n            Assert.Same(root, root.root);\n\n            Assert.Contains(config, root.AllConfigs);\n            Assert.Contains(config, root.GetConfigs(\"SOME_NODE\"));\n        }\n\n        [Fact]\n        public void TestCreateConfig__Url__FileAlreadyExists()\n        {\n            UrlDir root = UrlBuilder.CreateRoot();\n\n            ConfigNode node1 = new TestConfigNode(\"SOME_NODE\")\n            {\n                { \"name\", \"blah\" },\n                { \"foo\", \"bar\" },\n            };\n\n            ConfigNode node2 = new TestConfigNode(\"SOME_OTHER_NODE\")\n            {\n                { \"name\", \"bleh\" },\n                { \"jazz\", \"hands\" },\n            };\n\n            UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(\"abc/def\", node1, root);\n            UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(\"abc/def\", node2, root);\n\n            UrlDir.UrlFile file = config1.parent;\n            Assert.NotNull(file);\n\n            Assert.Same(file, config2.parent);\n            \n            Assert.Equal(\"def\", file.name);\n            Assert.Equal(\"cfg\", file.fileExtension);\n            Assert.Equal(UrlDir.FileType.Config, file.fileType);\n            Assert.Contains(config1, file.configs);\n            Assert.Contains(config2, file.configs);\n\n            UrlDir parent = file.parent;\n            Assert.NotNull(parent);\n            Assert.Equal(\"abc\", parent.name);\n            Assert.Contains(file, parent.files);\n\n            Assert.Contains(parent, root.children);\n\n            Assert.Same(root, file.root);\n            Assert.Same(root, parent.root);\n            Assert.Same(root, root.root);\n\n            Assert.Contains(config1, root.AllConfigs);\n            Assert.Contains(config1, root.GetConfigs(\"SOME_NODE\"));\n\n            Assert.Contains(config2, root.AllConfigs);\n            Assert.Contains(config2, root.GetConfigs(\"SOME_OTHER_NODE\"));\n        }\n    }\n}\n"
  },
  {
    "path": "TestUtilsTests/packages.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"xunit\" version=\"2.4.1\" targetFramework=\"net471\" />\n  <package id=\"xunit.abstractions\" version=\"2.0.3\" targetFramework=\"net471\" />\n  <package id=\"xunit.analyzers\" version=\"0.10.0\" targetFramework=\"net471\" />\n  <package id=\"xunit.assert\" version=\"2.4.1\" targetFramework=\"net471\" />\n  <package id=\"xunit.core\" version=\"2.4.1\" targetFramework=\"net471\" />\n  <package id=\"xunit.extensibility.core\" version=\"2.4.1\" targetFramework=\"net471\" />\n  <package id=\"xunit.extensibility.execution\" version=\"2.4.1\" targetFramework=\"net471\" />\n  <package id=\"xunit.runner.console\" version=\"2.4.1\" targetFramework=\"net471\" developmentDependency=\"true\" />\n  <package id=\"xunit.runner.visualstudio\" version=\"2.4.3\" targetFramework=\"net471\" developmentDependency=\"true\" />\n</packages>"
  },
  {
    "path": "Tests/AltEdits.cfg",
    "content": "\nMMTEST\n{\n\tname = altEdits\n\tMODULE\n\t{\n\t\tname = module1\n\t}\n\tMODULE\n\t{\n\t\tname = module2\n\t}\n}\n\n// Adds value to module2\n@MMTEST[altEdits]\n{\n\t@MODULE[module2]\n\t{\n\t\taddedValue = added\n\t\t+copyNothing = novalue\n\t}\n\t+MODULE[module1] \n\t{\n\t\t@name = moduleCopy\n\t}\n\t-MODULE[module1] { }\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = altEdits\n\t\tMODULE\n\t\t{\n\t\t\tname = module2\n\t\t\taddedValue = added\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = moduleCopy\n\t\t}\n\t}\n}\t"
  },
  {
    "path": "Tests/BadlyFormed.cfg",
    "content": "\nMMTEST\n{\n\tname = badFormat\n\tMODULE\n\t{\n\t\tname = module1\n\t}\n\t// Brackets without name\n\t//MODULE\n\t{\n\t\tname = module2\n\t}\n\ttestval = testthing\n\t{\n\t\t// name without value (ignored)\n\t\tsubval\n\t}\n\tdouble_brace\n\t{\n\t\t{\n\t\t\tone = 1\n\t\t}\n\t\t{\n\t\t\ttwo = 2\n\t\t}\n\t}\n}\n\n// Adds value to module2\n@MMTEST[badFormat]\n{\n\t@MODULE[module1]\n\t{\n\t\taddedValue = added\n\t}\n\t@[module2]\n\t{\n\t\tkey = Key\n\t}\n}\n\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = badFormat\n\t\ttestval = testthing\n\t\tMODULE\n\t\t{\n\t\t\tname = module1\n\t\t\taddedValue = added\n\t\t}\n\t\t\n\t\t{\n\t\t\tname = module2\n\t\t\tkey = Key\n\t\t}\n\t\t\n\t\t{\n\t\t}\n\t\t\n\t\tdouble_brace\n\t\t{\n\t\t\t{\n\t\t\t\tone = 1\n\t\t\t}\n\t\t\t{\n\t\t\t\ttwo = 2\n\t\t\t}\n\t\t}\t\t\n\t}\n}\t"
  },
  {
    "path": "Tests/NameLessNode.cfg",
    "content": "MMTEST\n{\n\tname = NameLessNode\n}\n\n@MMTEST[NameLessNode]\n{\n\t// Insert a name Less node\n    %MODEL {\n        %key = value\n    }\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = NameLessNode\n\t\tMODEL\n\t\t{\n\t\t\tkey = value\n\t\t}\t\t\n\t}\n}\t"
  },
  {
    "path": "Tests/Needs.cfg",
    "content": "\nMMTEST\n{\n\tname = testNeeds\n\tMODULE\n\t{\n\t\tname = module1\n\t}\n\tMODULE\n\t{\n\t\tname = module2\n\t}\n}\n\n// Adds value to module2\n@MMTEST[testNeeds]:NEEDS[!ModuleManager]\n{\n\t@MODULE[module2]\n\t{\n\t\taddedValue = added\n\t}\n}\n\n@MMTEST[testNeeds]:NEEDS[ModuleManager]\n{\n\t@MODULE[module1]\n\t{\n\t\taddedValue = added\n\t}\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = testNeeds\n\t\tMODULE\n\t\t{\n\t\t\tname = module1\n\t\t\taddedValue = added\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module2\n\t\t}\n\t}\n}\t"
  },
  {
    "path": "Tests/NodeCopy.cfg",
    "content": "\nMMTEST\n{\n\tname = nodeCopy\n\tMODULE\n\t{\n\t\tname = module1\n\t}\n}\n\n// Adds value to module2\n@MMTEST[nodeCopy]\n{\n\t// Copy module 1\n\t+MODULE\n\t{\n\t\t@name = module2\n\t}\n\t// Copy new module 2\n\t+MODULE[module2],0\n\t{\n\t\t@name = module3\n\t}\n\t// Copy with wildcard\n\t+MODULE,*\n\t{\n\t\t// $ matches the end - good for adding suffixes\n\t\t@name ^= :$:duplicate:\n\t\ttag = duplicate\n\t}\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = nodeCopy\n\t\tMODULE\n\t\t{\n\t\t\tname = module1\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module2\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module3\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module1duplicate\n\t\t\ttag = duplicate\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module2duplicate\n\t\t\ttag = duplicate\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module3duplicate\n\t\t\ttag = duplicate\n\t\t}\n\t}\n}\t"
  },
  {
    "path": "Tests/NodeCreate.cfg",
    "content": "MMTEST\n{\n\tname = CreateNode\n\tNODE\n\t{\n\t\tname = TestNode1\n\t\t\n\t\tvalue = test1\n\t}\n}\n\n@MMTEST[CreateNode]\n{\n\t&NODE[TestNode1]\n\t{\n\t\t%value = test2\n\t}\n\t&NODE[TestNode2]\n\t{\n\t\t%value = test3\n\t}\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST\n\t{\n\t\tname = CreateNode\n\t\tNODE\n\t\t{\n\t\t\tname = TestNode1\n\t\t\tvalue = test1\n\t\t}\n\t\tNODE\n\t\t{\n\t\t\tname = TestNode2\n\t\t\tvalue = test3\n\t\t}\n\t}\n}"
  },
  {
    "path": "Tests/NodeDelete.cfg",
    "content": "\nMMTEST\n{\n\tname = nodeDelete\n\tMODULE\n\t{\n\t\tname = module1\n\t}\n\tMODULE\n\t{\n\t\tname = module2\n\t\tkey = copy0\n\t}\n\tMODULE\n\t{\t\n\t\tname = module2\n\t\tkey = copy1\n\t}\n\tMODULE\n\t{\t\n\t\tname = module2\n\t\tkey = copy2\n\t}\n\tMODULE\n\t{\t\n\t\tname = module2\n\t\tkey = copy3\n\t}\n\tMODULE\n\t{\n\t\tname = module3\n\t}\n\tMODULE\n\t{\n\t\tname = module4\n\t}\n\tMODULE\n\t{\t\n\t\tname = tailEnd\n\t}\n}\n\n// Adds value to module2\n@MMTEST[nodeDelete]\n{\n\t// Delete the first copy\n\t-MODULE[module2] {}\n\t// Indexed delete\n\t-MODULE,2 { }\t\n\t// Indexed delete from end\n\t-MODULE,-2 { }\n\t// Indexed delete off end\n\t-MODULE,9999 { }\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = nodeDelete\n\t\tMODULE\n\t\t{\n\t\t\tname = module1\n\t\t}\n\t\tMODULE\n\t\t{\t\n\t\t\tname = module2\n\t\t\tkey = copy1\n\t\t}\n\t\tMODULE\n\t\t{\t\n\t\t\tname = module2\n\t\t\tkey = copy3\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module3\n\t\t}\n\t}\n}\t"
  },
  {
    "path": "Tests/NodeEdit.cfg",
    "content": "\nMMTEST\n{\n\tname = nodeEdit\n\tMODULE\n\t{\n\t\tname = module1\n\t}\n\tMODULE\n\t{\n\t\tname = module2\n\t\tkey = firstCopy\n\t}\n\tMODULE\n\t{\n\t\tname = module2\n\t\tkey = secondCopy\n\t}\n}\n\n// Adds value to module2\n@MMTEST[nodeEdit]\n{\n\t// edit by name\n\t@MODULE[module2]\n\t{\n\t\taddTo = firstCopy\n\t}\n\t// edit by index\n\t@MODULE,1\n\t{\n\t\taddTo = module2.1\n\t}\n\t// edit by name and index\n\t@MODULE[module2],1\n\t{\n\t\taddTo = module2.2\n\t}\n\t// edit by wildcard and index (off end)\n\t@MODULE[*2],5\n\t{\n\t\taddTo = module2.2again\n\t}\t\t\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = nodeEdit\n\t\tMODULE\n\t\t{\n\t\t\tname = module1\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module2\n\t\t\tkey = firstCopy\n\t\t\taddTo = firstCopy\n\t\t\taddTo = module2.1\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module2\n\t\t\tkey = secondCopy\n\t\t\taddTo = module2.2\n\t\t\taddTo = module2.2again\n\t\t}\n\t}\n}\t"
  },
  {
    "path": "Tests/NodeEditWildcard.cfg",
    "content": "\nMMTEST\n{\n\tname = nodeEditWildcard\n\tMODULE\n\t{\n\t\tname = module1\n\t}\n\tMODULE\n\t{\n\t\tname = module2\n\t\tkey = firstCopy\n\t}\n\tMODULE\n\t{\n\t\tname = module2\n\t\tkey = secondCopy\n\t}\n}\n\n// Adds value to module2\n@MMTEST[nodeEditWildcard]\n{\n\t// edit by name\n\t@MODULE[module*],*\n\t{\n\t\taddTo = allName\n\t}\n\t// edit by index\n\t@MODULE,*\n\t{\n\t\taddTo = allIndex\n\t}\n\t// edit by name and index\n\t@MODULE[module2],*\n\t{\n\t\taddTo = module2\n\t}\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = nodeEditWildcard\n\t\tMODULE\n\t\t{\n\t\t\tname = module1\n\t\t\taddTo = allName\n\t\t\taddTo = allIndex\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module2\n\t\t\tkey = firstCopy\n\t\t\taddTo = allName\n\t\t\taddTo = allIndex\n\t\t\taddTo = module2\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module2\n\t\t\tkey = secondCopy\n\t\t\taddTo = allName\n\t\t\taddTo = allIndex\n\t\t\taddTo = module2\n\t\t}\n\t}\n}\t"
  },
  {
    "path": "Tests/NodeHas.cfg",
    "content": "\nMMTEST\n{\n\tname = nodeHas\n\tMODULE\n\t{\n\t\tname = module1\n\t}\n\tMODULE\n\t{\n\t\tname = module2\n\t\tkey = firstCopy\n\t\tvalue = 5\n\t}\n\tMODULE\n\t{\n\t\tname = module2\n\t\tkey = secondCopy\n\t\tvalue = 2\n\t}\n}\n\n// Adds value to module2\n@MMTEST[nodeHas]\n{\n\t\n\t@MODULE[module2]:HAS[#key[secondCopy]]\n\t{\n\t\taddTo = secondCopy\n\t}\n\t\n\t@MODULE:HAS[#value[<5]]\n\t{\n\t\taddTo = secondCopy.value\n\t}\n\t\n\t@MODULE:HAS[#value[>1]]\n\t{\n\t\taddTo = firstCopy.secondCopy\n\t}\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST\n\t{\n\t\tname = nodeHas\n\t\tMODULE\n\t\t{\n\t\t\tname = module1\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module2\n\t\t\tkey = firstCopy\n\t\t\tvalue = 5\n\t\t\taddTo = firstCopy.secondCopy\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module2\n\t\t\tkey = secondCopy\n\t\t\tvalue = 2\n\t\t\taddTo = secondCopy\n\t\t\taddTo = secondCopy.value\n\t\t\taddTo = firstCopy.secondCopy\n\t\t}\n\t}\n}\t"
  },
  {
    "path": "Tests/NodeInsert.cfg",
    "content": "\nMMTEST\n{\n\tname = nodeInsert\n\tMODULE\n\t{\n\t\tname = module1\n\t}\n}\n\n// Adds value to module2\n@MMTEST[nodeInsert]\n{\n\t// Added at end\n\tMODULE\n\t{\n\t\tname = module2\n\t}\n\t// Insert at start\n\tMODULE,0\n\t{\n\t\tname = module0\n\t}\n\t// Insert off the end\n\tMODULE,9999\n\t{\n\t\tname = module999\n\t}\n\t\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = nodeInsert\n\t\tMODULE\n\t\t{\n\t\t\tname = module0\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module1\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module2\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module999\n\t\t}\n\t}\n}\t"
  },
  {
    "path": "Tests/NodeReplace.cfg",
    "content": "\nMMTEST\n{\n\tname = nodeReplace\n\tMODULE\n\t{\n\t\tname = module1\n\t}\n\tMODULE\n\t{\n\t\tname = module2\n\t\tkey = firstCopy\n\t}\n\tMODULE\n\t{\n\t\tname = module2\n\t\tkey = secondCopy\n\t}\n}\n\n// Adds value to module2\n@MMTEST[nodeReplace]\n{\n\t// replace by name\n\t%MODULE[module2]\n\t{\n\t\t%name = module2replaced\n\t\t%key = replaceFirstCopy\n\t}\n\t// replace by index\n\t%MODULE,0\n\t{\n\t\t%name = module1\n\t\t%addTo = replacedModule1\n\t}\n\t// Replace non-existent\n\t%MODULE[module3]\n\t{\n\t\t%name = module3\n\t\t@wontFind = value\n\t}\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = nodeReplace\n\t\tMODULE\n\t\t{\n\t\t\tname = module1\n\t\t\taddTo = replacedModule1\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module2replaced\n\t\t\tkey = replaceFirstCopy\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module2\n\t\t\tkey = secondCopy\n\t\t}\n\t\tMODULE\n\t\t{\n\t\t\tname = module3\n\t\t}\n\t}\n}\t"
  },
  {
    "path": "Tests/ValueCopy.cfg",
    "content": "\nMMTEST\n{\n\tname = valueCopy\n\tMODULE\n\t{\n\t\tname = module1\n\t\tmultiVal = one\n\t\tmultiVal = two\n\t\tnumeric = 0\n\t}\n}\n\n// Adds value to module2\n@MMTEST[valueCopy]\n{\n\t// Copy new module 2\n\t@MODULE[module1]\n\t{\n\t\t// Unindexed\n\t\t+multiVal = three\n\t\t// regexp with index\n\t\t+multiVal,1 ^= :$:duplicate:\n\t\t// Arithmetic\n\t\t+numeric += 5\n\t}\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = valueCopy\n\t\tMODULE\n\t\t{\n\t\t\tname = module1\n\t\t\tmultiVal = one\n\t\t\tmultiVal = two\n\t\t\tnumeric = 0\n\t\t\tmultiVal = three\n\t\t\tmultiVal = twoduplicate\n\t\t\tnumeric = 5\n\t\t}\n\t}\n}\t"
  },
  {
    "path": "Tests/ValueCreate.cfg",
    "content": "MMTEST\n{\n\tname = valueCreate\n\tvalue1 = test1\n\tvalue2 = test2\n\tNODE\n\t{\n\t\tname = testNode\n\t\t\n\t\tvalue3 = test3\n\t\tvalue4 = test4\n\t}\n}\n\n@MMTEST[valueCreate]\n{\n\t&value1 = test5\n\t&value2 = #$NODE[testNode]/value3$\n\t&value5 = test6\n\t&value6 = #$NODE[testNode]/value4$\n}\n\n@MMTEST[valueCreate]\n{\n\t!NODE[testNode] {}\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST\n\t{\n\t\tname = valueCreate\n\t\tvalue1 = test1\n\t\tvalue2 = test2\n\t\tvalue5 = test6\n\t\tvalue6 = test4\n\t}\n}"
  },
  {
    "path": "Tests/ValueDelete.cfg",
    "content": "\nMMTEST\n{\n\tname = valueDelete\n\tMODULE\n\t{\n\t\tname = module1\n\t\tmultiVal = one\n\t\tmultiVal = two\n\t\tmultiVal2 = one\n\t\tmultiVal2 = two\n\t\tnumeric = 0\n\t}\n}\n\n// Adds value to module2\n@MMTEST[valueDelete]\n{\n\t// Copy new module 2\n\t@MODULE[module1]\n\t{\n\t\t// Unindexed (remove all)\n\t\t-multiVal = dummy\n\t\t// Indexed\n\t\t-multiVal2,0 = dummy\n\t\t// Wildcard\n\t\t-num*ic = dummy\n\t}\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = valueDelete\n\t\tMODULE\n\t\t{\n\t\t\tname = module1\n\t\t\tmultiVal2 = two\n\t\t}\n\t}\n}\t"
  },
  {
    "path": "Tests/ValueEdit.cfg",
    "content": "\nMMTEST\n{\n\tname = valueEdit\n\tMODULE\n\t{\n\t\tname = module1\n\t\tmultiVal = one\n\t\tmultiVal = two\n\t\tnumeric = 0\n\t}\n}\n\n// Adds value to module2\n@MMTEST[valueEdit]\n{\n\t// Copy new module 2\n\t@MODULE[module1]\n\t{\n\t\t// Unindexed\n\t\t@name = module2\n\t\t// Unindexed for multi - defaults to zero\n\t\t@multiVal = oneEdit\n\t\t// regexp with index\n\t\t@multiVal,1 ^= :tw:mo:\n\t\t// Arithmetic\n\t\t@numeric += 5\n\t\t@numeric *= 20\n\t}\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = valueEdit\n\t\tMODULE\n\t\t{\n\t\t\tname = module2\n\t\t\tmultiVal = oneEdit\n\t\t\tmultiVal = moo\n\t\t\tnumeric = 100\n\t\t}\n\t}\n}\t"
  },
  {
    "path": "Tests/ValueEmpty.cfg",
    "content": "\nMMTEST\n{\n\tname = valueEmpty\n\tMODULE\n\t{\n\t\tname = module1\n\t\temptyVal = notEmptyYet\n\t}\n}\n\n// Changes value to empty\n@MMTEST[valueEmpty]\n{\n\t@MODULE[module1]\n\t{\n\t\t// Set to empty\n\t\t@emptyVal = \n\t}\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = valueEmpty\n\t\tMODULE\n\t\t{\n\t\t\tname = module1\n\t\t\temptyVal = \n\t\t}\n\t}\n}\t"
  },
  {
    "path": "Tests/ValueInsert.cfg",
    "content": "\nMMTEST\n{\n\tname = valueInsert\n\tMODULE\n\t{\n\t\tname = module1\n\t\tmultiVal = one\n\t\tmultiVal = two\n\t\tnumeric = 0\n\t}\n}\n\n// Adds value to module2\n@MMTEST[valueInsert]\n{\n\t// Copy new module 2\n\t@MODULE[module1]\n\t{\n\t\t// Unindexed\n\t\tmultiVal = three\n\t\t// Indexed\n\t\tmultiVal,0 = zero\n\t\t// Indexed off end\n\t\tmultiVal,999 = four\n\t}\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = valueInsert\n\t\tMODULE\n\t\t{\n\t\t\tname = module1\n\t\t\tnumeric = 0\n\t\t\tmultiVal = zero\n\t\t\tmultiVal = one\n\t\t\tmultiVal = two\n\t\t\tmultiVal = three\n\t\t\tmultiVal = four\n\t\t}\n\t}\n}\t"
  },
  {
    "path": "Tests/ValueReplace.cfg",
    "content": "\nMMTEST\n{\n\tname = valueReplace\n\tMODULE\n\t{\n\t\tname = module1\n\t\tmultiVal = one\n\t\tmultiVal = two\n\t\tnumeric = 0\n\t}\n}\n\n// Adds value to module2\n@MMTEST[valueReplace]\n{\n\t// Copy new module 2\n\t@MODULE[module1]\n\t{\n\t\t// Replace has by nature pretty limited capabilities\n\t\t// value present\n\t\t%multiVal = replaced\n\t\t// Value not present\n\t\t%hedgehog = spiky\n\t}\n}\n\nMMTEST_EXPECT\n{\n\tMMTEST \n\t{\n\t\tname = valueReplace\n\t\tMODULE\n\t\t{\n\t\t\tname = module1\n\t\t\tnumeric = 0\n\t\t\tmultiVal = replaced\n\t\t\thedgehog = spiky\n\t\t}\n\t}\n}\t"
  }
]