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; if (valid && splitDependency.Length == 2) { string versionString = splitDependency[1].Replace("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 = (VersionSpecificity)targetVersionNums.Length; 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; 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 { gadget.Gadget.HarmonyInstance.CreateClassProcessor(type).Patch(); patches++; } catch (Exception e) { Logger.LogError("Exception running patch '" + type.Name + "' for Gadget '" + gadget.Attribute.Name + "': " + Environment.NewLine + e.ToString()); } } }); Logger.Log("Performed " + patches + " patches for " + gadget.Attribute.Name); } 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."); 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.modRegistering = gadget.Gadget.ModID; gadget.Gadget.Initialize(); } 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.modRegistering = -1; } } Logger.Log("Done initializing Gadgets."); QueuedGadgets.Clear(); }
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(); }