/// <summary> /// Unloads the given GadgetMod. /// Cannot fully release the mod's RAM usage, as the old instance of the mod assembly that is stored in RAM cannot be fully removed. /// WARNING: Will trigger an immediate garbage collection, causing a short freeze! /// </summary> public static void UnloadMod(GadgetMod mod) { try { if (mod.Name == "GadgetCore") { throw new InvalidOperationException(GadgetMods.GetModByAssembly(Assembly.GetCallingAssembly()).Name + " seriously just tried to unload GadgetCore... Really?"); } List <GadgetMod> modsToUnload = mod.LoadedGadgets.SelectMany(x => Gadgets.LoadOrderTree.Find(x).FlattenUniqueByBreadth().Where(y => y != x)).Where(x => x != null).Select(x => x.Mod).Distinct().ToList(); bool wasBatchLoading = BatchLoading; BatchLoading = true; foreach (GadgetMod modToUnload in modsToUnload) { UnloadModInternal(modToUnload); } mod.Unload(); GC.Collect(); BatchLoading = wasBatchLoading; DisableQueuedGadgets(); Gadgets.SortGadgets(); } catch (Exception e) { Logger.LogError("Error unloading " + mod.Name + ": " + e); } }
internal static void RegisterMod(GadgetMod Mod) { Mod.Enabled = !GadgetCoreConfig.enabledMods.ContainsKey(Mod.Name) || GadgetCoreConfig.enabledMods[Mod.Name]; ModsByName.Add(Mod.Name, Mod); ModsByPath.Add(Mod.ModPath, Mod); ModsByAssembly.Add(Mod.Assembly, Mod); }
/// <summary> /// Soft-reloads the given GadgetMod. (Does not load it freshly from the disk). /// Also refreshes all Gadgets that are dependent on these Gadgets. /// Be warned that this only reloads the mod's <see cref="Gadget"/> instances, so some mods may not function as expected after a refresh. /// </summary> public static GadgetMod RefreshMod(GadgetMod mod) { foreach (GadgetInfo gadget in mod.LoadedGadgets) { Gadgets.ReloadGadget(gadget); } return(mod); }
internal static void DisposeAll() { foreach (GadgetModFile reference in openReferences) { GadgetMod Mod = reference.Mod; GadgetCore.CoreLogger.Log("WARNING: Forcibly disposing of GadgetModFile that " + Mod.Name + " failed to dispose of!"); reference.Dispose(); } }
internal static void DisposeAll(GadgetMod mod) { foreach (GadgetModFile reference in openReferences) { if (reference.Mod == mod) { GadgetCore.CoreLogger.Log("WARNING: Forcibly disposing of GadgetModFile that " + mod.Name + " failed to dispose of: " + reference.FilePath); reference.Dispose(); } } }
/// <summary> /// Loads and registers the <see cref="GadgetMod"/> represented by the given directory. Will log a warning and return null if the given directory is not a valid <see cref="GadgetMod"/>. Will also add the name to <see cref="ErroredMods"/> if the directory was valid, but an error occured during the mod-loading process. /// </summary> public static GadgetMod LoadModDir(string modDir) { string modName = null; try { string manifestFile = Path.Combine(modDir, "Manifest.ini"); if (File.Exists(manifestFile)) { if (Directory.Exists(Path.Combine(modDir, "Libs"))) { foreach (string lib in Directory.GetFiles(Path.Combine(modDir, "Libs"))) { string existingFilePath = Path.Combine(GadgetPaths.LibsPath, Path.GetFileName(lib)); FileInfo libFile = new FileInfo(lib); if (!File.Exists(existingFilePath) || libFile.LastWriteTime > new FileInfo(existingFilePath).LastWriteTime) { libFile.CopyTo(existingFilePath, true); } } } IniData manifest = manifestIniParser.ReadFile(manifestFile); modName = manifest["Metadata"]["Name"]; Assembly modAssembly = Assembly.LoadFile(Path.Combine(modDir, manifest["Metadata"]["Assembly"])); GadgetCore.LoadedAssemblies[modAssembly.GetName().Name] = modAssembly; GadgetMod mod = new GadgetMod(modDir, manifest, modAssembly); GadgetMods.RegisterMod(mod); if (!BatchLoading) { LoadGadgetMod(mod); } return(mod); } else { Logger.LogWarning("Invalid or non-mod directory '" + Path.GetFileName(modDir) + "' in Mods directory!"); } } catch (Exception e) { Logger.LogError("Exception loading mod directory '" + Path.GetFileName(modDir) + "':"); Logger.LogError(e.ToString()); if (modName != null) { Tuple <string, string> erroredMod = Tuple.Create(modName, modDir); if (!m_ErroredMods.Contains(erroredMod)) { m_ErroredMods.Add(erroredMod); } } } return(null); }
private static void UnloadModInternal(GadgetMod mod) { foreach (GadgetMod dependency in GadgetMods.ListAllMods().Where(x => x.ModDependencies.Contains(mod.Name))) { if (dependency.Name != "GadgetCore") { UnloadModInternal(mod); } } mod.IsLoaded = false; GadgetMods.UnregisterMod(mod); GadgetCore.LoadedAssemblies.Remove(mod.Assembly.GetName().Name); }
internal static void UnregisterMod(GadgetMod Mod) { ModsByName.Remove(Mod.Name); ModsByPath.Remove(Mod.ModPath); ModsByAssembly.Remove(Mod.Assembly); bool wasBatchLoading = GadgetLoader.BatchLoading; GadgetLoader.BatchLoading = true; foreach (GadgetInfo gadget in Mod.LoadedGadgets.ToList()) { Gadgets.UnregisterGadget(gadget); GadgetLoader.QueuedGadgets.Add(gadget); } GadgetLoader.BatchLoading = wasBatchLoading; if (!GadgetLoader.BatchLoading) { GadgetLoader.DisableQueuedGadgets(); Gadgets.SortGadgets(); } }
/// <summary> /// Sets the Enabled status of the given mod. Note that this can be queried using GadgetMod.Enabled /// </summary> public static void SetEnabled(GadgetMod mod, bool enabled) { if (mod.Enabled == enabled) { return; } mod.Enabled = enabled; GadgetCoreConfig.enabledMods[mod.Name] = enabled; bool wasBatchLoading = GadgetLoader.BatchLoading; GadgetLoader.BatchLoading = true; if (!enabled) { foreach (GadgetInfo gadget in mod.LoadedGadgets) { Gadgets.SetEnabled(gadget, false); } } GadgetLoader.BatchLoading = wasBatchLoading; GadgetCoreConfig.Update(); }
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(); }
/// <summary> /// Hard-reloads the given GadgetMod. (Loads it freshly from the disk). /// Also reloads all mods that are dependent on this mod. /// Causes a permanent increase in the game's RAM usage, as the old instance of the mod assembly that is stored in RAM cannot be fully removed. /// WARNING: Will trigger an immediate GC collection, causing a short freeze! /// </summary> public static GadgetMod ReloadMod(ref GadgetMod mod) { return(mod = ReloadMod(mod)); }
/// <summary> /// Hard-reloads the given GadgetMod. (Loads it freshly from the disk). /// Also reloads all mods that are dependent on this mod. /// Causes a permanent increase in the game's RAM usage, as the old instance of the mod assembly that is stored in RAM cannot be fully removed. /// WARNING: Will trigger an immediate GC collection, causing a short freeze! /// </summary> public static GadgetMod ReloadMod(GadgetMod mod) { try { if (mod.Name == "GadgetCore") { throw new InvalidOperationException(GadgetMods.GetModByAssembly(Assembly.GetCallingAssembly()).Name + " seriously just tried to reload GadgetCore... Really?"); } string modPath = mod.ModPath; List <GadgetMod> modsToReload = mod.LoadedGadgets.SelectMany(x => Gadgets.LoadOrderTree.Find(x).FlattenUniqueByBreadth()).Where(x => x != null).Select(x => x.Mod).Distinct().ToList(); foreach (GadgetMod modToUnload in modsToReload) { if (modToUnload.Name != "GadgetCore") { UnloadModInternal(modToUnload); } } GC.Collect(); BatchLoading = true; Logger.Log("Loading mod files..."); for (int i = 0; i < modsToReload.Count; i++) { if (modsToReload[i].Name != "GadgetCore") { if (modsToReload[i].IsArchive) { if (modsToReload[i].ModPath == modPath) { modsToReload[i] = mod = LoadModFile(modsToReload[i].ModPath); } else { modsToReload[i] = LoadModFile(modsToReload[i].ModPath); } } else { if (modsToReload[i].ModPath == modPath) { modsToReload[i] = mod = LoadModDir(modsToReload[i].ModPath); } else { modsToReload[i] = LoadModDir(modsToReload[i].ModPath); } } } else { RefreshMod(modsToReload[i]); } } Logger.Log("Done loading mod files."); Logger.Log("Loading mods..."); foreach (GadgetMod modToReload in modsToReload) { LoadGadgetMod(modToReload); } Logger.Log("Done loading mods."); Logger.Log("Sorting Gadgets..."); Gadgets.SortGadgets(); Logger.Log("Done sorting Gadgets."); Logger.Log("Queueing Gadgets for initialization..."); foreach (GadgetInfo gadget in modsToReload.SelectMany(x => x.LoadedGadgets)) { QueuedGadgets.Add(gadget); } Logger.Log("Done queueing Gadgets."); EnableQueuedGadgets(); BatchLoading = false; } catch (Exception e) { Logger.LogError("Error reloading " + mod.Name + ": " + e); } return(mod); }
/// <summary> /// Finds the index of the given mod in the list of all mods. /// </summary> public static int IndexOfMod(GadgetMod mod) { return(Array.IndexOf(ModsByName.Values.ToArray(), mod)); }
/// <summary> /// Soft-reloads the given GadgetMod. (Does not load it freshly from the disk). /// Also refreshes all mods that are dependent on this mod. /// Be warned that this only reloads the mod's <see cref="Gadget"/> instances, so some mods may not function as expected after a refresh. /// </summary> public static GadgetMod RefreshMod(ref GadgetMod mod) { return(mod = RefreshMod(mod)); }
internal GadgetModFile(string FilePath, bool IsArchivedFile, GadgetMod Mod) { this.FilePath = FilePath; this.IsArchivedFile = IsArchivedFile; this.Mod = Mod; }
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; }
/// <summary> /// Loads and registers the <see cref="GadgetMod"/> represented by the given file. Will log a warning and return null if the given file is not a valid <see cref="GadgetMod"/>. Will also add the name to <see cref="ErroredMods"/> if the file was valid, but an error occured during the mod-loading process. /// </summary> public static GadgetMod LoadModFile(string modFile) { string modName = null; ZipFile modZip = null; try { modZip = new ZipFile(modFile); if (Path.GetExtension(modFile) == ".umfmod") { GadgetCore.CoreLib.DecryptUMFModFile(modZip); } if (modZip.ContainsEntry("Manifest.ini")) { if (modZip.ContainsEntry("Libs")) { foreach (ZipEntry lib in modZip.Entries.Where(x => !x.IsDirectory && x.FileName.StartsWith("Libs"))) { string existingFilePath = Path.Combine(GadgetPaths.GadgetCorePath, lib.FileName); if (!File.Exists(existingFilePath) || lib.ModifiedTime > new FileInfo(existingFilePath).LastWriteTime) { lib.Extract(GadgetPaths.GadgetCorePath, ExtractExistingFileAction.OverwriteSilently); } } } using (MemoryStream stream = new MemoryStream()) { modZip["Manifest.ini"].Extract(stream); stream.Position = 0; IniData manifest = manifestIniParser.ReadData(new StreamReader(stream)); modName = manifest["Metadata"]["Name"]; stream.SetLength(0); if (manifest["Metadata"]["Assembly"] != null && modZip.ContainsEntry(manifest["Metadata"]["Assembly"])) { modZip[manifest["Metadata"]["Assembly"]].Extract(stream); } else { Logger.LogWarning("Failed to load mod `" + modName + "` because " + (manifest["Metadata"]["Assembly"] != null ? "the assembly name in its manifest is not valid!" : "its manifest does not contain the name of its asembly!")); return(null); } Assembly modAssembly = Assembly.Load(stream.ToArray()); GadgetCore.LoadedAssemblies[modAssembly.GetName().Name] = modAssembly; GadgetMod mod = new GadgetMod(modFile, manifest, modAssembly, modZip); GadgetMods.RegisterMod(mod); if (!BatchLoading) { LoadGadgetMod(mod); } modZip = null; return(mod); } } else { Logger.LogWarning("Invalid or non-mod file '" + Path.GetFileName(modFile) + "' in Mods directory!"); } } catch (Exception e) { Logger.LogError("Exception loading mod file '" + Path.GetFileName(modFile) + "':"); Logger.LogError(e.ToString()); if (modName != null) { Tuple <string, string> erroredMod = Tuple.Create(modName, modFile); if (!m_ErroredMods.Contains(erroredMod)) { m_ErroredMods.Add(erroredMod); } } } finally { modZip?.Dispose(); } return(null); }