private static void LoadGadgetMod(GadgetMod mod)
 {
     Logger.Log("Loading mod '" + mod.Name + "'");
     if (mod.ModDependencies.Any(x => GadgetMods.GetModByName(x) == null))
     {
         m_IncompatibleMods.Add(mod);
         Logger.LogWarning("Aborted loading mod '" + mod.Name + "' because of the following missing dependencies: " + mod.ModDependencies.Where(x => GadgetMods.GetModByName(x) == null).Concat());
         return;
     }
     Type[] gadgetTypes = mod.Assembly.GetExportedTypes().Where(x => x.IsSubclassOf(typeof(Gadget)) && x.GetCustomAttributes(typeof(GadgetAttribute), true).FirstOrDefault() != null).ToArray();
     if (gadgetTypes.Length == 0)
     {
         m_EmptyMods.Add(mod);
         Logger.LogWarning("Aborted loading mod '" + mod.Name + "' because it does not contain any Gadgets");
     }
     mod.m_LoadedGadgets   = new List <GadgetInfo>();
     mod.LoadedGadgets     = new ReadOnlyCollection <GadgetInfo>(mod.m_LoadedGadgets);
     mod.m_UnloadedGadgets = new List <GadgetInfo>();
     mod.UnloadedGadgets   = new ReadOnlyCollection <GadgetInfo>(mod.m_UnloadedGadgets);
     for (int i = 0; i < gadgetTypes.Length; i++)
     {
         Type            type      = gadgetTypes[i];
         GadgetAttribute attribute = (GadgetAttribute)type.GetCustomAttributes(typeof(GadgetAttribute), true).FirstOrDefault();
         if (mod.m_LoadedGadgets.Any(x => x.Attribute.Name == attribute.Name))
         {
             throw new InvalidOperationException("It is illegal for a mod to contain multiple Gadgets with the same name: " + attribute.Name);
         }
         int[] targetVersionNums = attribute.TargetGCVersion.Split('.').Select(x => int.Parse(x)).ToArray();
         if (targetVersionNums.Length != 4)
         {
             Array.Resize(ref targetVersionNums, 4);
         }
         if ((attribute.GadgetCoreVersionSpecificity == VersionSpecificity.MAJOR && GadgetCoreAPI.currentVersionNums[0] == targetVersionNums[0] && (GadgetCoreAPI.currentVersionNums[1] > targetVersionNums[1] || (GadgetCoreAPI.currentVersionNums[1] == targetVersionNums[1] && (GadgetCoreAPI.currentVersionNums[2] > targetVersionNums[2] || (GadgetCoreAPI.currentVersionNums[2] == targetVersionNums[2] && GadgetCoreAPI.currentVersionNums[3] >= targetVersionNums[3]))))) ||
             (attribute.GadgetCoreVersionSpecificity == VersionSpecificity.MINOR && GadgetCoreAPI.currentVersionNums[0] == targetVersionNums[0] && GadgetCoreAPI.currentVersionNums[1] == targetVersionNums[1] && (GadgetCoreAPI.currentVersionNums[2] > targetVersionNums[2] || (GadgetCoreAPI.currentVersionNums[2] == targetVersionNums[2] && GadgetCoreAPI.currentVersionNums[3] >= targetVersionNums[3]))) ||
             (attribute.GadgetCoreVersionSpecificity == VersionSpecificity.NONBREAKING && GadgetCoreAPI.currentVersionNums[0] == targetVersionNums[0] && GadgetCoreAPI.currentVersionNums[1] == targetVersionNums[1] && GadgetCoreAPI.currentVersionNums[2] == targetVersionNums[2] && GadgetCoreAPI.currentVersionNums[3] >= targetVersionNums[3]) ||
             (attribute.GadgetCoreVersionSpecificity == VersionSpecificity.BUGFIX && GadgetCoreAPI.currentVersionNums[0] == targetVersionNums[0] && GadgetCoreAPI.currentVersionNums[1] == targetVersionNums[1] && GadgetCoreAPI.currentVersionNums[2] == targetVersionNums[2] && GadgetCoreAPI.currentVersionNums[3] == targetVersionNums[3]))
         {
             Gadget gadget = null;
             try
             {
                 gadget = Activator.CreateInstance(type) as Gadget;
             }
             catch (Exception e)
             {
                 Logger.LogWarning("Found Gadget that could not be constructed: " + attribute.Name + ", in mod: {" + mod.Name + "}: Error: " + e);
             }
             if (gadget != null)
             {
                 try
                 {
                     Logger.Log("Found Gadget to load: " + attribute.Name + ", in mod: {" + mod.Name + "}");
                     gadget.CreateSingleton(gadget);
                     GadgetInfo info = new GadgetInfo(gadget, attribute, mod);
                     gadget.Logger = new GadgetLogger(mod.Name, attribute.Name);
                     gadget.Config = new GadgetConfig(Path.Combine(GadgetPaths.ConfigsPath, mod.Name + ".ini"), attribute.Name);
                     Gadgets.RegisterGadget(info);
                     mod.m_LoadedGadgets.Add(info);
                     if (!BatchLoading)
                     {
                         QueuedGadgets.Add(info);
                         EnableQueuedGadgets();
                     }
                 }
                 catch (Exception e)
                 {
                     Logger.LogWarning("Found Gadget that had an error during registration: " + attribute.Name + ", in mod: {" + mod.Name + "}. Error: " + e);
                     if (Gadgets.GetGadgetInfo(attribute.Name) != null)
                     {
                         Gadgets.UnregisterGadget(Gadgets.GetGadgetInfo(attribute.Name));
                     }
                 }
             }
         }
         else
         {
             int rD = (int)attribute.GadgetCoreVersionSpecificity;
             Logger.LogWarning("Found Gadget with an incompatible version: " + attribute.Name + ", in mod: {" + mod.Name + "}. Requires version: " + new string(attribute.TargetGCVersion.TakeWhile(x => (x == '.' ? --rD : rD) > 0).ToArray()));
         }
     }
     mod.IsLoaded = true;
 }
        internal static void EnableQueuedGadgets()
        {
            List <GadgetInfo> preFilteredQueuedGadgets = QueuedGadgets;

            QueuedGadgets = new List <GadgetInfo>();
            foreach (GadgetInfo gadget in preFilteredQueuedGadgets)
            {
                bool valid = true;
                foreach (string dependency in gadget.Attribute.Dependencies)
                {
                    string[]   splitDependency  = dependency.Split(':');
                    GadgetInfo dependencyGadget = Gadgets.GetGadgetInfo(splitDependency[0]);
                    valid = dependencyGadget != null && dependencyGadget.Gadget.Enabled;
                    if (valid)
                    {
                        if (splitDependency.Length == 2)
                        {
                            string versionString     = splitDependency[1].TrimStart('v');
                            int[]  targetVersionNums = versionString.Split('.').Select(x => int.Parse(x)).ToArray();
                            if (targetVersionNums.Length > 4)
                            {
                                continue;
                            }
                            int[] actualVersionNums = dependencyGadget.Mod.Version.ToString().Split('.').Select(x => int.Parse(x)).ToArray();
                            if (targetVersionNums.Length != 4)
                            {
                                Array.Resize(ref targetVersionNums, 4);
                            }
                            if (actualVersionNums.Length != 4)
                            {
                                Array.Resize(ref actualVersionNums, 4);
                            }
                            valid = actualVersionNums[0] == targetVersionNums[0] && actualVersionNums[1] == targetVersionNums[1] && (actualVersionNums[2] > targetVersionNums[2] || (actualVersionNums[2] == targetVersionNums[2] && actualVersionNums[3] >= targetVersionNums[3]));
                        }
                        else if (splitDependency.Length == 3)
                        {
                            string versionString     = splitDependency[1].TrimStart('v');
                            int[]  targetVersionNums = versionString.Split('.').Select(x => int.Parse(x)).ToArray();
                            if (targetVersionNums.Length > 4)
                            {
                                continue;
                            }
                            int[] actualVersionNums = dependencyGadget.Mod.Version.ToString().Split('.').Select(x => int.Parse(x)).ToArray();
                            VersionSpecificity versionSpecificity;
                            try
                            {
                                versionSpecificity = (VersionSpecificity)Enum.Parse(typeof(VersionSpecificity), splitDependency[2], true);
                            }
                            catch (ArgumentException)
                            {
                                Logger.LogWarning("Gadget " + gadget.Attribute.Name + " has an improperly-formatted dependency version string: " + dependency);
                                versionSpecificity = VersionSpecificity.MINOR;
                            }
                            if (targetVersionNums.Length != 4)
                            {
                                Array.Resize(ref targetVersionNums, 4);
                            }
                            if (actualVersionNums.Length != 4)
                            {
                                Array.Resize(ref actualVersionNums, 4);
                            }
                            valid = (versionSpecificity == VersionSpecificity.MAJOR && actualVersionNums[0] == targetVersionNums[0] && (actualVersionNums[1] > targetVersionNums[1] || (actualVersionNums[1] == targetVersionNums[1] && (actualVersionNums[2] > targetVersionNums[2] || (actualVersionNums[2] == targetVersionNums[2] && actualVersionNums[3] >= targetVersionNums[3]))))) ||
                                    (versionSpecificity == VersionSpecificity.MINOR && actualVersionNums[0] == targetVersionNums[0] && actualVersionNums[1] == targetVersionNums[1] && (actualVersionNums[2] > targetVersionNums[2] || (actualVersionNums[2] == targetVersionNums[2] && actualVersionNums[3] >= targetVersionNums[3]))) ||
                                    (versionSpecificity == VersionSpecificity.NONBREAKING && actualVersionNums[0] == targetVersionNums[0] && actualVersionNums[1] == targetVersionNums[1] && actualVersionNums[2] == targetVersionNums[2] && actualVersionNums[3] >= targetVersionNums[3]) ||
                                    (versionSpecificity == VersionSpecificity.BUGFIX && actualVersionNums[0] == targetVersionNums[0] && actualVersionNums[1] == targetVersionNums[1] && actualVersionNums[2] == targetVersionNums[2] && actualVersionNums[3] == targetVersionNums[3]);
                        }
                    }
                }
                if (valid)
                {
                    QueuedGadgets.Add(gadget);
                }
                else
                {
                    gadget.Gadget.Enabled = false;
                    GadgetCoreConfig.enabledGadgets[gadget.Attribute.Name] = false;
                    Gadgets.UnregisterGadget(gadget);
                    Logger.LogWarning("Aborted loading Gadget " + gadget.Attribute.Name + " due to missing dependencies.");
                }
            }
            Logger.Log("Loading Gadget configs...");
            foreach (GadgetInfo gadget in QueuedGadgets.ToList())
            {
                Logger.Log("Loading Config for Gadget '" + gadget.Attribute.Name + "'");
                try
                {
                    gadget.Gadget.LoadConfig();
                }
                catch (Exception e)
                {
                    gadget.Gadget.Enabled = false;
                    GadgetCoreConfig.enabledGadgets[gadget.Attribute.Name] = false;
                    Gadgets.UnregisterGadget(gadget);
                    QueuedGadgets.Remove(gadget);
                    Logger.LogError("Exception Loading Config For Gadget '" + gadget.Attribute.Name + "':" + Environment.NewLine + e.ToString());
                }
            }
            Logger.Log("Done loading Gadget configs.");
            Logger.Log("Preparing Gadgets for patching...");
            foreach (GadgetInfo gadget in QueuedGadgets.ToList())
            {
                Logger.Log("PrePatching Gadget '" + gadget.Attribute.Name + "'");
                try
                {
                    gadget.Gadget.HarmonyInstance = new Harmony(gadget.Mod.Name + "." + gadget.Attribute.Name + ".gadget");
                    gadget.Gadget.PrePatch();
                }
                catch (Exception e)
                {
                    gadget.Gadget.Enabled = false;
                    GadgetCoreConfig.enabledGadgets[gadget.Attribute.Name] = false;
                    Gadgets.UnregisterGadget(gadget);
                    QueuedGadgets.Remove(gadget);
                    Logger.LogError("Exception PrePatching Gadget '" + gadget.Attribute.Name + "':" + Environment.NewLine + e.ToString());
                }
            }
            Logger.Log("Done preparing Gadgets for patching.");
            Logger.Log("Patching Gadgets...");
            foreach (GadgetInfo gadget in QueuedGadgets.ToList())
            {
                int patches = 0, totalMethods = 0, errorPatching = 0, targetMissing = 0;
                try
                {
                    gadget.Mod.Assembly.GetExportedTypes().Do(delegate(Type type)
                    {
                        HarmonyGadgetAttribute attribute = type.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(HarmonyGadgetAttribute)) as HarmonyGadgetAttribute;
                        if (attribute?.Gadget == gadget.Attribute.Name && attribute.RequiredGadgets.All(x => Gadgets.GetGadget(x) != null))
                        {
                            try
                            {
                                List <MethodInfo> methods = gadget.Gadget.HarmonyInstance.CreateClassProcessor(type).Patch();
                                totalMethods += methods?.Count ?? 0;
                                if (methods == null || methods.Count == 0)
                                {
                                    Logger.Log("Skipping patch '" + type.Name + "' for Gadget '" + gadget.Attribute.Name + "': Attribute target does not exist.");
                                    targetMissing++;
                                }
                                else
                                {
                                    patches++;
                                }
                            }
                            catch (Exception e)
                            {
                                if (e.InnerException == null || !e.InnerException.Message.EndsWith("returned an unexpected result: null"))
                                {
                                    Logger.LogError("Exception running patch '" + type.Name + "' for Gadget '" + gadget.Attribute.Name + "': " + Environment.NewLine + e.ToString());
                                    errorPatching++;
                                }
                                else
                                {
                                    Logger.Log("Skipping patch '" + type.Name + "' for Gadget '" + gadget.Attribute.Name + "': TargetMethod returned null.");
                                    targetMissing++;
                                }
                            }
                        }
                    });
                    Logger.Log("Performed " + patches + " patches for '" + gadget.Attribute.Name + "'" + (totalMethods > 0 ? $" ({totalMethods} total methods patched)" : string.Empty) + (errorPatching > 0 ? targetMissing > 0 ? $" ({errorPatching} skipped due to errors, {targetMissing} skipped due to missing targets)" : $" ({errorPatching} skipped due to errors)" : targetMissing > 0 ? $" ({targetMissing} skipped due to missing targets)" : string.Empty));
                }
                catch (Exception e)
                {
                    gadget.Gadget.Enabled = false;
                    GadgetCoreConfig.enabledGadgets[gadget.Attribute.Name] = false;
                    Gadgets.UnregisterGadget(gadget);
                    QueuedGadgets.Remove(gadget);
                    Logger.LogError("Exception patching Gadget '" + gadget.Attribute.Name + "':" + Environment.NewLine + e.ToString());
                }
            }
            Logger.Log("Done patching Gadgets.");
            bool logOverrideWarnings = false;

            foreach (MethodBase patchedMethod in Harmony.GetAllPatchedMethods())
            {
                HarmonyLib.Patches patches = Harmony.GetPatchInfo(patchedMethod);
                if (patches == null)
                {
                    continue;
                }
                Dictionary <string, string> owners = new Dictionary <string, string>();
                List <Patch> overridingPrefixes    = patches.Prefixes.Where(x => x.PatchMethod.ReturnType == typeof(bool) && (x.PatchMethod.GetCustomAttributes(true).FirstOrDefault(a => a.GetType() == typeof(HarmonyOverridesAttribute)) as HarmonyOverridesAttribute)?.Overrides.Length != 0).ToList();
                if (overridingPrefixes != null && overridingPrefixes.Count > 0)
                {
                    foreach (string ownerID in patches.Owners)
                    {
                        if (ownerID == GadgetCore.HarmonyInstance.Id)
                        {
                            owners[ownerID] = "GadgetCore";
                        }
                        else
                        {
                            string[] splitOwnerID = ownerID.Split('.');
                            if (splitOwnerID.Length == 3 && splitOwnerID[2] == "gadget")
                            {
                                GadgetMod  mod    = GadgetMods.GetModByName(splitOwnerID[0]);
                                GadgetInfo gadget = mod?.LoadedGadgets.SingleOrDefault(x => x.Attribute.Name == splitOwnerID[1]);
                                if (gadget != null)
                                {
                                    owners[ownerID] = $"'{splitOwnerID[1]}' from the mod {{{splitOwnerID[0]}}}";
                                }
                                else
                                {
                                    owners[ownerID] = $"Unrecognized patcher '{ownerID}'";
                                }
                            }
                            else
                            {
                                owners[ownerID] = $"Unrecognized patcher '{ownerID}'";
                            }
                        }
                    }
                    IEnumerable <Patch> problematicPrefixes = patches.Prefixes.Where(x => !overridingPrefixes.Contains(x) && (x.PatchMethod.GetCustomAttributes(true).FirstOrDefault(a => a.GetType() == typeof(HarmonyOverriddenAttribute)) as HarmonyOverriddenAttribute)?.Overrides.Length != 0 && overridingPrefixes.Any(
                                                                                         p => x.owner != p.owner &&
                                                                                         (x.PatchMethod.GetCustomAttributes(true).FirstOrDefault(a => a.GetType() == typeof(HarmonyOverriddenAttribute)) as HarmonyOverriddenAttribute)?.Overrides.Contains(p.owner) != true &&
                                                                                         (p.PatchMethod.GetCustomAttributes(true).FirstOrDefault(a => a.GetType() == typeof(HarmonyOverridesAttribute)) as HarmonyOverridesAttribute)?.Overrides.Contains(x.owner) != true &&
                                                                                         x.priority <= p.priority &&
                                                                                         !x.before.Contains(p.owner) &&
                                                                                         !p.after.Contains(x.owner)));
                    IEnumerable <Patch> problematicTranspilers = patches.Transpilers.Where(x => (x.PatchMethod.GetCustomAttributes(true).FirstOrDefault(a => a.GetType() == typeof(HarmonyOverriddenAttribute)) as HarmonyOverriddenAttribute)?.Overrides.Length != 0 && overridingPrefixes.Any(
                                                                                               p => x.owner != p.owner &&
                                                                                               (x.PatchMethod.GetCustomAttributes(true).FirstOrDefault(a => a.GetType() == typeof(HarmonyOverriddenAttribute)) as HarmonyOverriddenAttribute)?.Overrides.Contains(p.owner) != true &&
                                                                                               (p.PatchMethod.GetCustomAttributes(true).FirstOrDefault(a => a.GetType() == typeof(HarmonyOverridesAttribute)) as HarmonyOverridesAttribute)?.Overrides.Contains(x.owner) != true));
                    if (problematicPrefixes != null && problematicPrefixes.Any() || problematicTranspilers != null && problematicTranspilers.Any())
                    {
                        if (!logOverrideWarnings)
                        {
                            logOverrideWarnings = true;
                            Logger.LogWarning("Possibly problematic patch overrides detected!", false);
                        }
                        Logger.Log($"Patches to {patchedMethod.DeclaringType.FullName}.{patchedMethod.Name} by {overridingPrefixes.Select(x => owners[x.owner]).Concat()} may override the following patch{(problematicPrefixes.Count() + problematicTranspilers.Count() > 1 ? "es" : "")}:");
                        if (problematicPrefixes != null)
                        {
                            foreach (Patch patch in problematicPrefixes)
                            {
                                Logger.Log($" - Prefix from {owners[patch.owner]}");
                            }
                        }
                        if (problematicTranspilers != null)
                        {
                            foreach (Patch patch in problematicTranspilers)
                            {
                                Logger.Log($" - Transpiler from {owners[patch.owner]}");
                            }
                        }
                    }
                }
            }
            if (logOverrideWarnings)
            {
                Logger.Log("End of possibly problematic patch overrides.");
            }
            Logger.Log("Creating registries...");
            foreach (GadgetInfo gadget in QueuedGadgets.ToList())
            {
                if (gadget.Gadget.Enabled)
                {
                    foreach (Registry registry in gadget.Gadget.CreateRegistries())
                    {
                        GameRegistry.RegisterRegistry(registry);
                    }
                }
            }
            Logger.Log("Done creating registries.");
            Logger.Log("Initializing Gadgets...");
            foreach (GadgetInfo gadget in QueuedGadgets.ToList())
            {
                Logger.Log("Initializing Gadget '" + gadget.Attribute.Name + "'");
                try
                {
                    Registry.gadgetRegistering = gadget.Gadget.ModID;
                    gadget.Gadget.LoadInternal();
                }
                catch (Exception e)
                {
                    gadget.Gadget.Enabled = false;
                    GadgetCoreConfig.enabledGadgets[gadget.Attribute.Name] = false;
                    Gadgets.UnregisterGadget(gadget);
                    QueuedGadgets.Remove(gadget);
                    Logger.LogError("Exception Initializing Gadget '" + gadget.Attribute.Name + "':" + Environment.NewLine + e.ToString());
                }
                finally
                {
                    Registry.gadgetRegistering = -1;
                }
            }
            Logger.Log("Done initializing Gadgets.");
            QueuedGadgets.Clear();
            GadgetCoreConfig.Update();
        }