/// <summary> /// Gets the SpaceCore Assembly. /// </summary> /// <returns> The SpaceCore Assembly.</returns> private Assembly GetSpaceCoreAssembly() { IModInfo modData = Entry.Helper.ModRegistry.Get("spacechase0.SpaceCore"); object spaceCoreInstance = modData.GetType().GetProperty("Mod", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).GetValue(modData); return(spaceCoreInstance.GetType().Assembly); }
internal void LoadPPaFConfig() { PPaFModInfo = Helper.ModRegistry.Get("Amaranthacyan.PlatonicPartnersandFriendships"); if (PPaFModInfo == null) { Monitor.Log($"PPaF not installed, cannot sync config"); return; } PropertyInfo property = PPaFModInfo.GetType().GetProperty("DirectoryPath", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (property == null) { Monitor.Log($"Can't access directory path for PPaF", LogLevel.Error); return; } string directoryPath = property.GetValue(PPaFModInfo).ToString(); Monitor.Log($"Loading PPaF config from {directoryPath}", LogLevel.Debug); IContentPack contentPack = this.Helper.ContentPacks.CreateFake(directoryPath); PPaFModConfig pmc = contentPack.ReadJsonFile <PPaFModConfig>("config.json"); if (pmc.PlatonicNPCs.Length > 0) { Monitor.Log($"Platonic NPCs: {pmc.PlatonicNPCs}", LogLevel.Debug); Config.HuggingNPCs = Config.cslToList(pmc.PlatonicNPCs); UsingPPaFConfig = true; } }
/// <summary>Returns IContentPack from IModInfo.</summary> public static IContentPack GetContentPackFromModInfo(IModInfo modInfo) { if (!modInfo.IsContentPack) { throw new ArgumentException($"{modInfo.Manifest.UniqueID} is not a content pack"); } // ContentPack is a property of the internal interface IModMetadata // which is derived from IModInfo. Access it via reflection. return(modInfo.GetType().GetProperty("ContentPack").GetValue(modInfo) as IContentPack); }
private object GetJsonAssets() { IModHelper helper = ModEntry.HelperInstance; // get mod info IModInfo modInfo = helper.ModRegistry.Get("spacechase0.jsonAssets"); if (modInfo == null) { return(null); // mod isn't installed } // get mod instance var property = modInfo.GetType().GetProperty("Mod", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (property == null) { throw new InvalidOperationException($"Can't access 'Mod' field on type '{modInfo.GetType().FullName}'."); } return(property.GetValue(modInfo)); }
public override void Entry(IModHelper helper) { M.Monitor = Monitor; M.Helper = Helper; HarmonyInstance harmony = HarmonyInstance.Create(ModManifest.UniqueID); SpriteBatchOverrides.PatchAll(harmony); AssetDataForImageOverrides.PatchAll(harmony); IModInfo info = Helper.ModRegistry.Get("Pathoschild.ContentPatcher"); Mod cp = (Mod)info.GetType().GetProperty("Mod").GetValue(info); foreach (IContentPack pack in cp.Helper.ContentPacks.GetOwned()) { Data data = pack.ReadJsonFile <Data>("content.json"); if (data.ScaleRequests != null) { foreach (string res in data.ScaleRequests) { Requests.Add(Normalize(res)); } } } }
private void SetStableOverlays() { if (!Config.Water) { return; } seasonalVersion = SeasonalVersion.None; usingMyTextures = false; FilledTroughOverlay = null; if (Helper.ModRegistry.IsLoaded("sonreirblah.JBuildings")) { // seasonal overlays are assigned in LateDayStarted EmptyTroughOverlay = null; seasonalVersion = SeasonalVersion.Sonr; return; } if (Helper.ModRegistry.IsLoaded("Oklinq.CleanStable")) { EmptyTroughOverlay = new Lazy <Texture2D>(() => Helper.Content.Load <Texture2D>($"assets/overlay_empty.png", ContentSource.ModFolder)); return; } if (Helper.ModRegistry.IsLoaded("Elle.SeasonalBuildings")) { var data = Helper.ModRegistry.Get("Elle.SeasonalBuildings"); var path = data.GetType().GetProperty("DirectoryPath"); if (path != null && path.GetValue(data) != null) { var list = ReadConfigFile("config.json", path.GetValue(data) as string, new[] { "color palette", "stable" }, data.Manifest.Name); if (list["stable"] != "false") { EmptyTroughOverlay = new Lazy <Texture2D>(() => Helper.Content.Load <Texture2D>($"assets/elle/overlay_empty_{list["color palette"]}.png", ContentSource.ModFolder)); return; } } } if (Helper.ModRegistry.IsLoaded("Elle.SeasonalVanillaBuildings")) { var data = Helper.ModRegistry.Get("Elle.SeasonalVanillaBuildings"); var path = data.GetType().GetProperty("DirectoryPath"); if (path != null && path.GetValue(data) != null) { var list = ReadConfigFile("config.json", path.GetValue(data) as string, new[] { "stable" }, data.Manifest.Name); if (list["stable"] == "true") { FilledTroughOverlay = new Lazy <Texture2D>(() => Helper.Content.Load <Texture2D>($"assets/overlay_filled_tone.png", ContentSource.ModFolder)); EmptyTroughOverlay = new Lazy <Texture2D>(() => Helper.Content.Load <Texture2D>($"assets/overlay_empty_tone.png", ContentSource.ModFolder)); return; } } } if (Helper.ModRegistry.IsLoaded("Gweniaczek.Medieval_stables")) { IModInfo data = Helper.ModRegistry.Get("Gweniaczek.Medieval_stables"); var path = data.GetType().GetProperty("DirectoryPath"); if (path != null && path.GetValue(data) != null) { var dict = ReadConfigFile("config.json", path.GetValue(data) as string, new[] { "stableOption" }, data.Manifest.Name); SetupGwenTextures(dict); return; } } if (Helper.ModRegistry.IsLoaded("Gweniaczek.Medieval_buildings")) { var data = Helper.ModRegistry.Get("Gweniaczek.Medieval_buildings"); var path = data.GetType().GetProperty("DirectoryPath"); if (path != null && path.GetValue(data) != null) { var dict = ReadConfigFile("config.json", path.GetValue(data) as string, new[] { "buildingsReplaced", "stableOption" }, data.Manifest.Name); if (dict["buildingsReplaced"].Contains("stable")) { SetupGwenTextures(dict); return; } } } if (Helper.ModRegistry.IsLoaded("magimatica.SeasonalVanillaBuildings")) { EmptyTroughOverlay = new Lazy <Texture2D>(() => Helper.Content.Load <Texture2D>($"assets/overlay_empty_no_bucket.png", ContentSource.ModFolder)); return; } // no compatible texture mod found so we will use mine usingMyTextures = true; EmptyTroughOverlay = new Lazy <Texture2D>(() => Helper.Content.Load <Texture2D>($"assets/overlay_empty.png", ContentSource.ModFolder)); }
/// <summary>Checks whether a config file should be used with the currently loaded farm.</summary> /// <param name="config">The FarmConfig to be checked.</param> /// <param name="pack">The content pack associated with this file, if any.</param> /// <returns>True if the file should be used with the current farm; false otherwise.</returns> public static bool CheckFileConditions(FarmConfig config, IContentPack pack) { Monitor.Log("Checking file conditions...", LogLevel.Trace); //check farm type if (config.File_Conditions.FarmTypes != null && config.File_Conditions.FarmTypes.Length > 0) { Monitor.Log("Farm type condition(s) found. Checking...", LogLevel.Trace); bool validType = false; foreach (object obj in config.File_Conditions.FarmTypes) //for each listed farm type { int type = -1; //parse the farm type object into an integer (int type) if (obj is long || obj is int) //if the object is a readable integer { type = Convert.ToInt32(obj); //convert it to a 32-bit integer and use it } else if (obj is string name) //if the object is a string, cast it as one { if (name.Equals("All", StringComparison.OrdinalIgnoreCase) || name.Equals("Any", StringComparison.OrdinalIgnoreCase)) //if this is "all" or "any" { validType = true; break; //skip checking the rest of the farm types } else if (Enum.TryParse(name, true, out FarmTypes farmType)) //if this name can be parsed into a FarmTypes enum { type = (int)farmType; //use it as an integer } else //if this is a string, but not a recognized value { if (int.TryParse(name, out int parsed)) //if this string can be parsed as an integer { type = parsed; //use the parsed value } else //if this string cannot be parsed { Monitor.Log($"This setting in the Farm Types list could not be parsed: {name}", LogLevel.Debug); Monitor.Log($"The setting will be ignored. If it's intended to be a custom farm type, please use its ID number instead of its name.", LogLevel.Debug); continue; //skip to the next farm type condition } } } if (type == Game1.whichFarm) //if the parsed type matches the current farm type { validType = true; break; //skip checking the rest of the farm types } else if (Game1.whichFarm == 200) //if this may be a MTN farm type, handle compatibility (based on MTN 2.1.0-beta8) { //if MTN is installed, use reflection to access its "whichFarm" equivalent try { IModInfo modInfo = Helper.ModRegistry.Get("SgtPickles.MTN"); // get MTN info (null if it's not installed) if (modInfo == null) //if MTN isn't installed { continue; //skip to the next farm type check } object mod = modInfo.GetType().GetProperty("Mod", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(modInfo); //get MTN's Mod instance if (mod == null) //if it couldn't be accessed { continue; //skip to the next farm type check } object customManager = mod.GetType().GetProperty("CustomManager", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(mod); //get CustomManager instance if (customManager == null) //if it couldn't be accessed { continue; //skip to the next farm type check } object loadedFarm = customManager.GetType().GetProperty("LoadedFarm", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(customManager); //get LoadedFarm instance if (loadedFarm == null) //if it couldn't be accessed { continue; //skip to the next farm type check } int farmID = (int)loadedFarm.GetType().GetProperty("ID", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(loadedFarm); //get the loaded farm's ID if (type == farmID) //if MTN's custom farm ID matches the parsed type { Monitor.VerboseLog($"Farm type matches the loaded MTN farm type's ID: {type}"); validType = true; break; //skip checking the rest of the farm types } } catch (Exception) //if any exception is thrown while accessing MTN { Monitor.Log($"Error encountered while trying to check MTN farm type. This check may be obsolete or require updates.", LogLevel.Trace); continue; //skip to the next farm type check } } } if (validType) //if a valid farm type was listed { Monitor.Log("Farm type matched a setting. File allowed.", LogLevel.Trace); } else { Monitor.Log("Farm type did NOT match any settings. File disabled.", LogLevel.Trace); return(false); //prevent config use } } //check farmer name if (config.File_Conditions.FarmerNames != null && config.File_Conditions.FarmerNames.Length > 0) { Monitor.Log("Farmer name condition(s) found. Checking...", LogLevel.Trace); bool validName = false; foreach (string name in config.File_Conditions.FarmerNames) //for each listed name { if (name.Equals(Game1.player.Name, StringComparison.OrdinalIgnoreCase)) //if the name matches the current player's { validName = true; break; //skip the rest of these checks } } if (validName) //if a valid farmer name was listed { Monitor.Log("Farmer name matched a setting. File allowed.", LogLevel.Trace); } else { Monitor.Log("Farmer name did NOT match any settings. File disabled.", LogLevel.Trace); return(false); //prevent config use } } //check save file names (technically the save folder name) if (config.File_Conditions.SaveFileNames != null && config.File_Conditions.SaveFileNames.Length > 0) { Monitor.Log("Save file name condition(s) found. Checking...", LogLevel.Trace); bool validSave = false; foreach (string saveName in config.File_Conditions.SaveFileNames) //for each listed save name { if (saveName.Equals(Constants.SaveFolderName, StringComparison.OrdinalIgnoreCase)) //if the name matches the current player's save folder name { validSave = true; break; //skip the rest of these checks } } if (validSave) //if a valid save name was listed { Monitor.Log("Save file name matched a setting. File allowed.", LogLevel.Trace); } else { Monitor.Log("Save file name did NOT match any settings. File disabled.", LogLevel.Trace); return(false); //prevent config use } } //check whether other mods exist if (config.File_Conditions.OtherMods != null && config.File_Conditions.OtherMods.Count > 0) { Monitor.Log("Other mod condition(s) found. Checking...", LogLevel.Trace); bool validMods = true; //whether all entries are accurate (true by default, unlike most other settings) foreach (KeyValuePair <string, bool> entry in config.File_Conditions.OtherMods) //for each mod entry in OtherMods { bool validEntry = !(entry.Value); //whether the current entry is accurate (starts false if the mod should exist; starts true if the mod should NOT exist) foreach (IModInfo mod in Helper.ModRegistry.GetAll()) //for each mod currently loaded by SMAPI { if (entry.Value == true) //if the mod should exist { if (entry.Key.Equals(mod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase)) //if this mod's UniqueID matches the OtherMods entry { validEntry = true; //this entry is valid break; //skip the rest of these checks } } else //if the mod should NOT exist { if (entry.Key.Equals(mod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase)) //if this mod's UniqueID matches the OtherMods entry { validEntry = false; //this entry is invalid break; //skip the rest of these checks } } } if (validEntry) //if the current mod entry is valid { Monitor.Log($"Mod check successful: \"{entry.Key}\" {(entry.Value ? "does exist" : "does not exist")}.", LogLevel.Trace); } else //if the current mod entry is NOT valid { Monitor.Log($"Mod check failed: \"{entry.Key}\" {(entry.Value ? "does not exist" : "does exist")}.", LogLevel.Trace); validMods = false; break; //skip the rest of these checks } } if (validMods) //if all mod entries in the list are valid { Monitor.Log("The OtherMods list matches the player's mods. File allowed.", LogLevel.Trace); } else //if any entries were NOT valid { Monitor.Log("The OtherMods list does NOT match the player's mods. File disabled.", LogLevel.Trace); return(false); //prevent config use } } return(true); //all checks were successful; config should be used }
/// <summary>Checks whether a config file should be used with the currently loaded farm.</summary> /// <param name="config">The FarmConfig to be checked.</param> /// <param name="pack">The content pack associated with this file, if any.</param> /// <returns>True if the file should be used with the current farm; false otherwise.</returns> public static bool CheckFileConditions(FarmConfig config, IContentPack pack) { Monitor.Log("Checking file conditions...", LogLevel.Trace); //check "reset main data folder" flag //NOTE: it's preferable to do this as the first step; it's intended to be a one-off cleaning process, rather than a conditional effect if (config.File_Conditions.ResetMainDataFolder && MConfig.EnableContentPackFileChanges) //if "reset" is true and file changes are enabled { if (pack != null) //if this is part of a content pack { //attempt to load the content pack's global save data ContentPackSaveData packSave = null; try { packSave = pack.ReadJsonFile <ContentPackSaveData>(Path.Combine("data", "ContentPackSaveData.save")); //load the content pack's global save data (null if it doesn't exist) } catch (Exception ex) { Monitor.Log($"Warning: This content pack's save data could not be parsed correctly: {pack.Manifest.Name}", LogLevel.Warn); Monitor.Log($"Affected file: data/ContentPackSaveData.save", LogLevel.Warn); Monitor.Log($"Please delete the file and/or contact the mod's developer.", LogLevel.Warn); Monitor.Log($"The content pack will be skipped until this issue is fixed. The auto-generated error message is displayed below:", LogLevel.Warn); Monitor.Log($"----------", LogLevel.Warn); Monitor.Log($"{ex.Message}", LogLevel.Warn); return(false); //disable this content pack's config, since it may require this process to function } if (packSave == null) //no global save data exists for this content pack { packSave = new ContentPackSaveData(); } if (!packSave.MainDataFolderReset) //if this content pack has NOT reset the main data folder yet { Monitor.Log($"ResetMainDataFolder requested by content pack: {pack.Manifest.Name}", LogLevel.Debug); string dataPath = Path.Combine(Helper.DirectoryPath, "data"); //the path to this mod's data folder DirectoryInfo dataFolder = new DirectoryInfo(dataPath); //an object representing this mod's data directory if (dataFolder.Exists) //the data folder exists { Monitor.Log("Attempting to archive data folder...", LogLevel.Trace); try { string archivePath = Path.Combine(Helper.DirectoryPath, "data", "archive", DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss")); DirectoryInfo archiveFolder = Directory.CreateDirectory(archivePath); //create a timestamped archive folder foreach (FileInfo file in dataFolder.GetFiles()) //for each file in dataFolder { file.MoveTo(Path.Combine(archiveFolder.FullName, file.Name)); //move each file to archiveFolder } } catch (Exception ex) { Monitor.Log($"Warning: This content pack attempted to archive Farm Type Manager's data folder but failed: {pack.Manifest.Name}", LogLevel.Warn); Monitor.Log($"Please report this issue to Farm Type Manager's developer. This might also be fixed by manually removing your FarmTypeManager/data/ files.", LogLevel.Warn); Monitor.Log($"The content pack will be skipped until this issue is fixed. The auto-generated error message is displayed below:", LogLevel.Warn); Monitor.Log($"----------", LogLevel.Warn); Monitor.Log($"{ex.Message}", LogLevel.Warn); return(false); //disable this content pack's config, since it may require this process to function } } else //the data folder doesn't exist { Monitor.Log("Data folder not found; assuming it was deleted or not yet generated.", LogLevel.Trace); } packSave.MainDataFolderReset = true; //update save data } pack.WriteJsonFile(Path.Combine("data", "ContentPackSaveData.save"), packSave); //update the content pack's global save data file Monitor.Log("Data folder archive successful.", LogLevel.Trace); } else //if this is NOT part of a content pack { Monitor.Log("This farm's config file has ResetMainDataFolder = true, but this setting only works for content packs.", LogLevel.Info); } } //check farm type if (config.File_Conditions.FarmTypes != null && config.File_Conditions.FarmTypes.Length > 0) { Monitor.Log("Farm type condition(s) found. Checking...", LogLevel.Trace); bool validType = false; foreach (object obj in config.File_Conditions.FarmTypes) //for each listed farm type { int type = -1; //parse the farm type object into an integer (int type) if (obj is long || obj is int) //if the object is a readable integer { type = Convert.ToInt32(obj); //convert it to a 32-bit integer and use it } else if (obj is string name) //if the object is a string, cast it as one { if (name.Equals("All", StringComparison.OrdinalIgnoreCase) || name.Equals("Any", StringComparison.OrdinalIgnoreCase)) //if this is "all" or "any" { validType = true; break; //skip checking the rest of the farm types } else if (Enum.TryParse(name, true, out FarmTypes farmType)) //if this name can be parsed into a FarmTypes enum { type = (int)farmType; //use it as an integer } else //if this is a string, but not a recognized value { if (int.TryParse(name, out int parsed)) //if this string can be parsed as an integer { type = parsed; //use the parsed value } else //if this string cannot be parsed { Monitor.Log($"This setting in the Farm Types list could not be parsed: {name}", LogLevel.Debug); Monitor.Log($"The setting will be ignored. If it's intended to be a custom farm type, please use its ID number instead of its name.", LogLevel.Debug); continue; //skip to the next farm type condition } } } if (type == Game1.whichFarm) //if the parsed type matches the current farm type { validType = true; break; //skip checking the rest of the farm types } else if (Game1.whichFarm == 200) //if this may be a MTN farm type, handle compatibility (based on MTN 2.1.0-beta8) { //if MTN is installed, use reflection to access its "whichFarm" equivalent try { IModInfo modInfo = Helper.ModRegistry.Get("SgtPickles.MTN"); // get MTN info (null if it's not installed) if (modInfo == null) //if MTN isn't installed { continue; //skip to the next farm type check } object mod = modInfo.GetType().GetProperty("Mod", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(modInfo); //get MTN's Mod instance if (mod == null) //if it couldn't be accessed { continue; //skip to the next farm type check } object customManager = mod.GetType().GetProperty("CustomManager", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(mod); //get CustomManager instance if (customManager == null) //if it couldn't be accessed { continue; //skip to the next farm type check } object loadedFarm = customManager.GetType().GetProperty("LoadedFarm", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(customManager); //get LoadedFarm instance if (loadedFarm == null) //if it couldn't be accessed { continue; //skip to the next farm type check } int farmID = (int)loadedFarm.GetType().GetProperty("ID", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(loadedFarm); //get the loaded farm's ID if (type == farmID) //if MTN's custom farm ID matches the parsed type { Monitor.VerboseLog($"Farm type matches the loaded MTN farm type's ID: {type}"); validType = true; break; //skip checking the rest of the farm types } } catch (Exception) //if any exception is thrown while accessing MTN { Monitor.Log($"Error encountered while trying to check MTN farm type. This check may be obsolete or require updates.", LogLevel.Trace); continue; //skip to the next farm type check } } } if (validType) //if a valid farm type was listed { Monitor.Log("Farm type matched a setting. File allowed.", LogLevel.Trace); } else { Monitor.Log("Farm type did NOT match any settings. File disabled.", LogLevel.Trace); return(false); //prevent config use } } //check farmer name if (config.File_Conditions.FarmerNames != null && config.File_Conditions.FarmerNames.Length > 0) { Monitor.Log("Farmer name condition(s) found. Checking...", LogLevel.Trace); bool validName = false; foreach (string name in config.File_Conditions.FarmerNames) //for each listed name { if (name.Equals(Game1.player.Name, StringComparison.OrdinalIgnoreCase)) //if the name matches the current player's { validName = true; break; //skip the rest of these checks } } if (validName) //if a valid farmer name was listed { Monitor.Log("Farmer name matched a setting. File allowed.", LogLevel.Trace); } else { Monitor.Log("Farmer name did NOT match any settings. File disabled.", LogLevel.Trace); return(false); //prevent config use } } //check save file names (technically the save folder name) if (config.File_Conditions.SaveFileNames != null && config.File_Conditions.SaveFileNames.Length > 0) { Monitor.Log("Save file name condition(s) found. Checking...", LogLevel.Trace); bool validSave = false; foreach (string saveName in config.File_Conditions.SaveFileNames) //for each listed save name { if (saveName.Equals(Constants.SaveFolderName, StringComparison.OrdinalIgnoreCase)) //if the name matches the current player's save folder name { validSave = true; break; //skip the rest of these checks } } if (validSave) //if a valid save name was listed { Monitor.Log("Save file name matched a setting. File allowed.", LogLevel.Trace); } else { Monitor.Log("Save file name did NOT match any settings. File disabled.", LogLevel.Trace); return(false); //prevent config use } } //check whether other mods exist if (config.File_Conditions.OtherMods != null && config.File_Conditions.OtherMods.Count > 0) { Monitor.Log("Other mod condition(s) found. Checking...", LogLevel.Trace); bool validMods = true; //whether all entries are accurate (true by default, unlike most other settings) foreach (KeyValuePair <string, bool> entry in config.File_Conditions.OtherMods) //for each mod entry in OtherMods { bool validEntry = !(entry.Value); //whether the current entry is accurate (starts false if the mod should exist; starts true if the mod should NOT exist) foreach (IModInfo mod in Helper.ModRegistry.GetAll()) //for each mod currently loaded by SMAPI { if (entry.Value == true) //if the mod should exist { if (entry.Key.Equals(mod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase)) //if this mod's UniqueID matches the OtherMods entry { validEntry = true; //this entry is valid break; //skip the rest of these checks } } else //if the mod should NOT exist { if (entry.Key.Equals(mod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase)) //if this mod's UniqueID matches the OtherMods entry { validEntry = false; //this entry is invalid break; //skip the rest of these checks } } } if (validEntry) //if the current mod entry is valid { Monitor.Log($"Mod check successful: \"{entry.Key}\" {(entry.Value ? "does exist" : "does not exist")}.", LogLevel.Trace); } else //if the current mod entry is NOT valid { Monitor.Log($"Mod check failed: \"{entry.Key}\" {(entry.Value ? "does not exist" : "does exist")}.", LogLevel.Trace); validMods = false; break; //skip the rest of these checks } } if (validMods) //if all mod entries in the list are valid { Monitor.Log("The OtherMods list matches the player's mods. File allowed.", LogLevel.Trace); } else //if any entries were NOT valid { Monitor.Log("The OtherMods list does NOT match the player's mods. File disabled.", LogLevel.Trace); return(false); //prevent config use } } return(true); //all checks were successful; config should be used }
private void DiscoverBooks(IModInfo mod, bool owned) { string?path = null; // For our own Content Packs, books are located in Books/ rather than AlmanacBooks/ if (owned) { path = "Books"; } // For other mods, we need to detect an "Almanac:Books" key in their manifest before // we're willing to assume they have books. else if (mod.Manifest.ExtraFields != null && mod.Manifest.ExtraFields.TryGetValue("Almanac:Books", out object?value)) { // For a boolean value, use the default path. if (value is bool bv) { if (!bv) { return; } path = "AlmanacBooks"; // For a string value, use that path instead. } else if (value is string str) { path = str; } } // No path? No books. Leave! if (string.IsNullOrEmpty(path)) { return; } // Alright. Is this a ContentPack? IContentPack?pack = null; string root; if (mod.IsContentPack && mod.GetType().GetProperty("ContentPack", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(mod) is IContentPack cp) { pack = cp; root = cp.DirectoryPath; } else if (mod.GetType().GetProperty("DirectoryPath", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(mod) is string str) { root = str; } else { Log($"Unable to locate root directory of mod {mod.Manifest.UniqueID}", LogLevel.Warn); return; } string booksRoot = Path.Join(root, PathUtilities.NormalizePath(path)); if (!Directory.Exists(booksRoot)) { Log($"Book subdirectory does not exist for mod {mod.Manifest.UniqueID}", owned ? LogLevel.Trace : LogLevel.Warn); return; } foreach (string bookDir in Directory.EnumerateDirectories(booksRoot)) { string id = Path.GetRelativePath(booksRoot, bookDir); string bookFile = Path.Join(bookDir, "book.json"); if (!File.Exists(bookFile)) { Log($"Book subdirectory \"{id}\" has no book.json file.", LogLevel.Warn); continue; } // At this point, we definitely have a book file. Make sure we have // an IContentPack for reading its data. if (pack == null) { pack = Mod.Helper.ContentPacks.CreateTemporary( directoryPath: root, id: $"{Mod.ModManifest.UniqueID}.books.{mod.Manifest.UniqueID}", name: mod.Manifest.Name, description: mod.Manifest.Description, author: mod.Manifest.Author, version: mod.Manifest.Version ); } string localBookFile = Path.Join(path, id, "book.json"); Log($"Loading book \"{id}\" from {bookFile}", LogLevel.Trace); Book?book; try { book = pack.ReadJsonFile <Book>(localBookFile); if (book is null) { throw new ArgumentNullException("book"); } } catch (Exception ex) { Log($"Error reading book file for \"{id}\" from {mod.Manifest.UniqueID}", LogLevel.Error, ex); continue; } // Set the book's ID based on its providing mod and folder, if an ID wasn't set. if (book.Id == null) { book.Id = new NamespaceId(mod.Manifest.UniqueID, id); } // Store the book. string bid = book.Id.ToString(); if (RawBooks !.ContainsKey(bid)) { Log($"Found duplicate book for key \"{bid}\" from {mod.Manifest.UniqueID}", LogLevel.Warn); }