From d9c001a39f4562038fb608468169ea22cf074111 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 May 2021 12:06:18 -0400 Subject: fix version zero validation --- src/SMAPI/Framework/ModLoading/ModResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index c70820e4..7761a567 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -173,7 +173,7 @@ namespace StardewModdingAPI.Framework.ModLoading if (string.IsNullOrWhiteSpace(mod.Manifest.Name)) missingFields.Add(nameof(IManifest.Name)); - if (mod.Manifest.Version == null || mod.Manifest.Version.ToString() == "0.0") + if (mod.Manifest.Version == null || mod.Manifest.Version.ToString() == "0.0.0") missingFields.Add(nameof(IManifest.Version)); if (string.IsNullOrWhiteSpace(mod.Manifest.UniqueID)) missingFields.Add(nameof(IManifest.UniqueID)); -- cgit From 4b391d631ccde301969ae0c04f407a01ba4055b1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 May 2021 12:12:03 -0400 Subject: normalize manifest array fields --- src/SMAPI/Framework/ModLoading/ModMetadata.cs | 19 +++++++++++-------- src/SMAPI/Framework/ModLoading/ModResolver.cs | 2 +- src/SMAPI/Framework/SCore.cs | 19 ++++++++----------- 3 files changed, 20 insertions(+), 20 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index 17e6d59a..b703f74c 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -195,7 +195,10 @@ namespace StardewModdingAPI.Framework.ModLoading /// public IEnumerable GetUpdateKeys(bool validOnly = false) { - foreach (string rawKey in this.Manifest?.UpdateKeys ?? new string[0]) + if (this.Manifest == null) + yield break; + + foreach (string rawKey in this.Manifest.UpdateKeys) { UpdateKey updateKey = UpdateKey.Parse(rawKey); if (updateKey.LooksValid || !validOnly) @@ -251,16 +254,16 @@ namespace StardewModdingAPI.Framework.ModLoading { var ids = new Dictionary(StringComparer.OrdinalIgnoreCase); - // yield dependencies - if (this.Manifest?.Dependencies != null) + if (this.Manifest != null) { - foreach (var entry in this.Manifest?.Dependencies) + // yield dependencies + foreach (IManifestDependency entry in this.Manifest.Dependencies) ids[entry.UniqueID] = entry.IsRequired; - } - // yield content pack parent - if (this.Manifest?.ContentPackFor?.UniqueID != null) - ids[this.Manifest.ContentPackFor.UniqueID] = true; + // yield content pack parent + if (this.Manifest.ContentPackFor?.UniqueID != null) + ids[this.Manifest.ContentPackFor.UniqueID] = true; + } return ids; } diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index 7761a567..3bf2b0a1 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -82,7 +82,7 @@ namespace StardewModdingAPI.Framework.ModLoading // get update URLs List updateUrls = new List(); - foreach (string key in mod.Manifest.UpdateKeys ?? new string[0]) + foreach (string key in mod.Manifest.UpdateKeys) { string url = getUpdateUrl(key); if (url != null) diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 5862b112..8b2c7544 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1593,19 +1593,16 @@ namespace StardewModdingAPI.Framework // validate dependencies // Although dependencies are validated before mods are loaded, a dependency may have failed to load. - if (mod.Manifest.Dependencies?.Any() == true) + foreach (IManifestDependency dependency in mod.Manifest.Dependencies.Where(p => p.IsRequired)) { - foreach (IManifestDependency dependency in mod.Manifest.Dependencies.Where(p => p.IsRequired)) + if (this.ModRegistry.Get(dependency.UniqueID) == null) { - if (this.ModRegistry.Get(dependency.UniqueID) == null) - { - string dependencyName = mods - .FirstOrDefault(otherMod => otherMod.HasID(dependency.UniqueID)) - ?.DisplayName ?? dependency.UniqueID; - errorReasonPhrase = $"it needs the '{dependencyName}' mod, which couldn't be loaded."; - failReason = ModFailReason.MissingDependencies; - return false; - } + string dependencyName = mods + .FirstOrDefault(otherMod => otherMod.HasID(dependency.UniqueID)) + ?.DisplayName ?? dependency.UniqueID; + errorReasonPhrase = $"it needs the '{dependencyName}' mod, which couldn't be loaded."; + failReason = ModFailReason.MissingDependencies; + return false; } } -- cgit From 7c76c5cad2c6edac75d6387dbaa5999295be46d8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 May 2021 12:13:39 -0400 Subject: add validation for the manifest 'Dependencies' field --- src/SMAPI/Framework/ModLoading/ModResolver.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index 3bf2b0a1..2f506571 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -188,6 +188,28 @@ namespace StardewModdingAPI.Framework.ModLoading // validate ID format if (!PathUtilities.IsSlug(mod.Manifest.UniqueID)) mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, "its manifest specifies an invalid ID (IDs must only contain letters, numbers, underscores, periods, or hyphens)."); + + // validate dependencies + foreach (var dependency in mod.Manifest.Dependencies) + { + // null dependency + if (dependency == null) + { + mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest has a null entry under {nameof(IManifest.Dependencies)}."); + continue; + } + + // missing ID + if (string.IsNullOrWhiteSpace(dependency.UniqueID)) + { + mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest has a {nameof(IManifest.Dependencies)} entry with no {nameof(IManifestDependency.UniqueID)} field."); + continue; + } + + // invalid ID + if (!PathUtilities.IsSlug(dependency.UniqueID)) + mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest has a {nameof(IManifest.Dependencies)} entry with an invalid {nameof(IManifestDependency.UniqueID)} field (IDs must only contain letters, numbers, underscores, periods, or hyphens)."); + } } // validate IDs are unique -- cgit From 4ac04ee3aca00f7c1e105ddabd1ed16995b17086 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 May 2021 12:19:30 -0400 Subject: fix error if a mod has a 'Dependencies' entry with no ID --- src/SMAPI/Framework/ModLoading/ModMetadata.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index b703f74c..0ace084f 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -195,7 +195,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// public IEnumerable GetUpdateKeys(bool validOnly = false) { - if (this.Manifest == null) + if (!this.HasManifest()) yield break; foreach (string rawKey in this.Manifest.UpdateKeys) @@ -254,14 +254,17 @@ namespace StardewModdingAPI.Framework.ModLoading { var ids = new Dictionary(StringComparer.OrdinalIgnoreCase); - if (this.Manifest != null) + if (this.HasManifest()) { // yield dependencies foreach (IManifestDependency entry in this.Manifest.Dependencies) - ids[entry.UniqueID] = entry.IsRequired; + { + if (!string.IsNullOrWhiteSpace(entry.UniqueID)) + ids[entry.UniqueID] = entry.IsRequired; + } // yield content pack parent - if (this.Manifest.ContentPackFor?.UniqueID != null) + if (!string.IsNullOrWhiteSpace(this.Manifest.ContentPackFor?.UniqueID)) ids[this.Manifest.ContentPackFor.UniqueID] = true; } -- cgit From c310875f902f82ef3b9486e2ff3705106b2ad40c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 19 May 2021 23:38:10 -0400 Subject: fix 'loaded with custom settings' message shown with default settings --- src/SMAPI/Framework/Models/SConfig.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index a71bafd9..10bf9f94 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -22,7 +22,7 @@ namespace StardewModdingAPI.Framework.Models [nameof(VerboseLogging)] = false, [nameof(LogNetworkTraffic)] = false, [nameof(RewriteMods)] = true, - [nameof(AggressiveMemoryOptimizations)] = true + [nameof(AggressiveMemoryOptimizations)] = false }; /// The default values for , to log changes if different. -- cgit From b149e11338ba2dbaf030a70783d28b6be21ccf1e Mon Sep 17 00:00:00 2001 From: DiscipleOfEris Date: Wed, 26 May 2021 11:50:49 -0700 Subject: Add `World.FurnitureListChanged` event Create a new event available to SMAPI mods to track furniture changes. To facilitate the event, a `FurnitureListChangedEventArgs` class is added as well. Fixes #778 --- src/SMAPI/Framework/Events/EventManager.cs | 4 ++++ src/SMAPI/Framework/Events/ModWorldEvents.cs | 7 +++++++ src/SMAPI/Framework/SCore.cs | 4 ++++ src/SMAPI/Framework/StateTracking/LocationTracker.cs | 7 ++++++- src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs | 4 ++++ 5 files changed, 25 insertions(+), 1 deletion(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index f4abfffe..dfc289ed 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -162,6 +162,9 @@ namespace StardewModdingAPI.Framework.Events /// Raised after terrain features (like floors and trees) are added or removed in a location. public readonly ManagedEvent TerrainFeatureListChanged; + /// Raised after furniture are added or removed in a location. + public readonly ManagedEvent FurnitureListChanged; + /**** ** Specialized ****/ @@ -238,6 +241,7 @@ namespace StardewModdingAPI.Framework.Events this.ObjectListChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.ObjectListChanged)); this.ChestInventoryChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.ChestInventoryChanged)); this.TerrainFeatureListChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.TerrainFeatureListChanged)); + this.FurnitureListChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.FurnitureListChanged)); this.LoadStageChanged = ManageEventOf(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.LoadStageChanged)); this.UnvalidatedUpdateTicking = ManageEventOf(nameof(IModEvents.Specialized), nameof(ISpecializedEvents.UnvalidatedUpdateTicking), isPerformanceCritical: true); diff --git a/src/SMAPI/Framework/Events/ModWorldEvents.cs b/src/SMAPI/Framework/Events/ModWorldEvents.cs index 21b1b664..f4c40abc 100644 --- a/src/SMAPI/Framework/Events/ModWorldEvents.cs +++ b/src/SMAPI/Framework/Events/ModWorldEvents.cs @@ -65,6 +65,13 @@ namespace StardewModdingAPI.Framework.Events remove => this.EventManager.TerrainFeatureListChanged.Remove(value); } + /// Raised after furniture are added or removed in a location. + public event EventHandler FurnitureListChanged + { + add => this.EventManager.FurnitureListChanged.Add(value, this.Mod); + remove => this.EventManager.FurnitureListChanged.Remove(value); + } + /********* ** Public methods diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 8b2c7544..bf88798b 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -914,6 +914,10 @@ namespace StardewModdingAPI.Framework // terrain features changed if (locState.TerrainFeatures.IsChanged) events.TerrainFeatureListChanged.Raise(new TerrainFeatureListChangedEventArgs(location, locState.TerrainFeatures.Added, locState.TerrainFeatures.Removed)); + + // furniture changed + if (locState.Furniture.IsChanged) + events.FurnitureListChanged.Raise(new FurnitureListChangedEventArgs(location, locState.Furniture.Added, locState.Furniture.Removed)); } } diff --git a/src/SMAPI/Framework/StateTracking/LocationTracker.cs b/src/SMAPI/Framework/StateTracking/LocationTracker.cs index 519fe8f4..6d3a62bb 100644 --- a/src/SMAPI/Framework/StateTracking/LocationTracker.cs +++ b/src/SMAPI/Framework/StateTracking/LocationTracker.cs @@ -48,6 +48,9 @@ namespace StardewModdingAPI.Framework.StateTracking /// Tracks added or removed terrain features. public IDictionaryWatcher TerrainFeaturesWatcher { get; } + /// Tracks added or removed furniture. + public ICollectionWatcher FurnitureWatcher { get; } + /// Tracks items added or removed to chests. public IDictionary ChestWatchers { get; } = new Dictionary(); @@ -68,6 +71,7 @@ namespace StardewModdingAPI.Framework.StateTracking this.NpcsWatcher = WatcherFactory.ForNetCollection(location.characters); this.ObjectsWatcher = WatcherFactory.ForNetDictionary(location.netObjects); this.TerrainFeaturesWatcher = WatcherFactory.ForNetDictionary(location.terrainFeatures); + this.FurnitureWatcher = WatcherFactory.ForNetCollection(location.furniture); this.Watchers.AddRange(new IWatcher[] { @@ -76,7 +80,8 @@ namespace StardewModdingAPI.Framework.StateTracking this.LargeTerrainFeaturesWatcher, this.NpcsWatcher, this.ObjectsWatcher, - this.TerrainFeaturesWatcher + this.TerrainFeaturesWatcher, + this.FurnitureWatcher }); this.UpdateChestWatcherList(added: location.Objects.Pairs, removed: new KeyValuePair[0]); diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs index 6ae52fd0..6c9cc4f5 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs @@ -34,6 +34,9 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots /// Tracks added or removed terrain features. public SnapshotListDiff> TerrainFeatures { get; } = new SnapshotListDiff>(); + /// Tracks added or removed furniture. + public SnapshotListDiff Furniture { get; } = new SnapshotListDiff(); + /// Tracks changed chest inventories. public IDictionary ChestItems { get; } = new Dictionary(); @@ -59,6 +62,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots this.Npcs.Update(watcher.NpcsWatcher); this.Objects.Update(watcher.ObjectsWatcher); this.TerrainFeatures.Update(watcher.TerrainFeaturesWatcher); + this.Furniture.Update(watcher.FurnitureWatcher); // chest inventories this.ChestItems.Clear(); -- cgit From 66f8920c29567de615dbcb0a06e78f774d128f6b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 24 Jun 2021 20:17:34 -0400 Subject: log trace message if conflicting software is detected --- src/SMAPI/Framework/SCore.cs | 55 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index bf88798b..2de2b21a 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -11,6 +11,9 @@ using System.Security; using System.Text; using System.Threading; using System.Threading.Tasks; +#if SMAPI_FOR_WINDOWS +using Microsoft.Win32; +#endif using Microsoft.Xna.Framework; #if SMAPI_FOR_XNA using System.Windows.Forms; @@ -376,6 +379,9 @@ namespace StardewModdingAPI.Framework mods = resolver.ProcessDependencies(mods, modDatabase).ToArray(); this.LoadMods(mods, this.Toolkit.JsonHelper, this.ContentCore, modDatabase); + // check for software likely to cause issues + this.CheckForSoftwareConflicts(); + // check for updates this.CheckForUpdatesAsync(mods); } @@ -1251,6 +1257,55 @@ namespace StardewModdingAPI.Framework this.LogManager.SetConsoleTitle(consoleTitle); } + /// Log a warning if software known to cause issues is installed. + private void CheckForSoftwareConflicts() + { +#if SMAPI_FOR_WINDOWS + this.Monitor.Log("Checking for known software conflicts..."); + + try + { + string[] registryKeys = { @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall", @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" }; + + string[] installedNames = registryKeys + .SelectMany(registryKey => + { + using RegistryKey key = Registry.LocalMachine.OpenSubKey(registryKey); + if (key == null) + return new string[0]; + + return key + .GetSubKeyNames() + .Select(subkeyName => + { + using RegistryKey subkey = key.OpenSubKey(subkeyName); + string displayName = (string)subkey?.GetValue("DisplayName"); + string displayVersion = (string)subkey?.GetValue("DisplayVersion"); + + if (displayName != null && displayVersion != null && displayName.EndsWith($" {displayVersion}")) + displayName = displayName.Substring(0, displayName.Length - displayVersion.Length - 1); + + return displayName; + }) + .ToArray(); + }) + .Where(name => name != null && (name.Contains("MSI Afterburner") || name.Contains("RivaTuner"))) + .Distinct() + .OrderBy(name => name) + .ToArray(); + + if (installedNames.Any()) + this.Monitor.Log($" Found {string.Join(" and ", installedNames)} installed, which can conflict with SMAPI. If you experience errors or crashes, try disabling that software or adding an exception for SMAPI / Stardew Valley."); + else + this.Monitor.Log(" None found!"); + } + catch (Exception ex) + { + this.Monitor.Log($"Failed when checking for conflicting software. Technical details:\n{ex}"); + } +#endif + } + /// Asynchronously check for a new version of SMAPI and any installed mods, and print alerts to the console if an update is available. /// The mods to include in the update check (if eligible). private void CheckForUpdatesAsync(IModMetadata[] mods) -- cgit From 5e3a1abbd421196ee63442c82cb6606b6630a6ca Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 26 Jun 2021 11:16:12 -0400 Subject: improve error if SMAPI fails to dispose on exit --- src/SMAPI/Framework/SCore.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 2de2b21a..c3285979 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -297,7 +297,14 @@ namespace StardewModdingAPI.Framework } finally { - this.Dispose(); + try + { + this.Dispose(); + } + catch (Exception ex) + { + this.Monitor.Log($"The game ended, but SMAPI wasn't able to dispose correctly. Technical details: {ex}", LogLevel.Error); + } } } -- cgit