diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs
index 8ef5e4a8..1080a888 100644
--- a/src/SMAPI/Framework/ModLoading/ModResolver.cs
+++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs
@@ -242,12 +242,32 @@ namespace StardewModdingAPI.Framework.ModLoading
}
}
+ /// Apply preliminary overrides to the load order based on the SMAPI configuration.
+ /// The mods to process.
+ /// The mod IDs SMAPI should load before any other mods (except those needed to load them).
+ /// The mod IDs SMAPI should load after any other mods.
+ public IModMetadata[] ApplyLoadOrderOverrides(IModMetadata[] mods, HashSet modIdsToLoadEarly, HashSet modIdsToLoadLate)
+ {
+ if (!modIdsToLoadEarly.Any() && !modIdsToLoadLate.Any())
+ return mods;
+
+ return mods
+ .OrderBy(mod =>
+ {
+ string id = mod.Manifest.UniqueID;
+ if (modIdsToLoadEarly.Contains(id))
+ return -1;
+ if (modIdsToLoadLate.Contains(id))
+ return 1;
+ return 0;
+ })
+ .ToArray();
+ }
+
/// Sort the given mods by the order they should be loaded.
/// The mods to process.
/// Handles access to SMAPI's internal mod metadata list.
- /// The mod IDs SMAPI should try to load early, before any other mods not included in this list.
- /// The mod IDs SMAPI should try to load late, after all other mods not included in this list.
- public IEnumerable ProcessDependencies(IEnumerable mods, IReadOnlyList modIdsToLoadEarly, IReadOnlyList modIdsToLoadLate, ModDatabase modDatabase)
+ public IEnumerable ProcessDependencies(IReadOnlyList mods, ModDatabase modDatabase)
{
// initialize metadata
mods = mods.ToArray();
@@ -262,28 +282,8 @@ namespace StardewModdingAPI.Framework.ModLoading
}
// sort mods
- IModMetadata[] allMods = mods.ToArray();
- IModMetadata[] modsToLoadEarly = modIdsToLoadEarly
- .Where(modId => !modIdsToLoadLate.Contains(modId))
- .Select(modId => allMods.FirstOrDefault(m => m.Manifest.UniqueID == modId))
- .Where(m => m != null)
- .Select(m => m!)
- .ToArray();
- IModMetadata[] modsToLoadLate = modIdsToLoadLate
- .Where(modId => !modIdsToLoadEarly.Contains(modId))
- .Select(modId => allMods.FirstOrDefault(m => m.Manifest.UniqueID == modId))
- .Where(m => m != null)
- .Select(m => m!)
- .ToArray();
- IModMetadata[] modsToLoadAsUsual = allMods.Where(m => !modsToLoadEarly.Contains(m) && !modsToLoadLate.Contains(m)).ToArray();
-
- List orderSortedMods = new();
- orderSortedMods.AddRange(modsToLoadEarly);
- orderSortedMods.AddRange(modsToLoadAsUsual);
- orderSortedMods.AddRange(modsToLoadLate);
-
- foreach (IModMetadata mod in orderSortedMods)
- this.ProcessDependencies(orderSortedMods, modDatabase, mod, states, sortedMods, new List());
+ foreach (IModMetadata mod in mods)
+ this.ProcessDependencies(mods, modDatabase, mod, states, sortedMods, new List());
return sortedMods.Reverse();
}
diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs
index 40d3450f..ee2dc18d 100644
--- a/src/SMAPI/Framework/Models/SConfig.cs
+++ b/src/SMAPI/Framework/Models/SConfig.cs
@@ -82,11 +82,11 @@ namespace StardewModdingAPI.Framework.Models
/// The mod IDs SMAPI should ignore when performing update checks or validating update keys.
public HashSet SuppressUpdateChecks { get; set; }
- /// The mod IDs SMAPI should try to load early, before any other mods not included in this list.
- public List ModsToLoadEarly { get; set; }
+ /// The mod IDs SMAPI should load before any other mods (except those needed to load them).
+ public HashSet ModsToLoadEarly { get; set; }
- /// The mod IDs SMAPI should try to load late, after all other mods not included in this list.
- public List ModsToLoadLate { get; set; }
+ /// The mod IDs SMAPI should load after any other mods.
+ public HashSet ModsToLoadLate { get; set; }
/********
@@ -106,8 +106,8 @@ namespace StardewModdingAPI.Framework.Models
/// The colors to use for text written to the SMAPI console.
/// Whether to prevent mods from enabling Harmony's debug mode, which impacts performance and creates a file on your desktop. Debug mode should never be enabled by a released mod.
/// The mod IDs SMAPI should ignore when performing update checks or validating update keys.
- /// The mod IDs SMAPI should try to load early, before any other mods not included in this list.
- /// The mod IDs SMAPI should try to load late, after all other mods not included in this list.
+ /// The mod IDs SMAPI should load before any other mods (except those needed to load them).
+ /// The mod IDs SMAPI should load after any other mods.
public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks, string[]? modsToLoadEarly, string[]? modsToLoadLate)
{
this.DeveloperMode = developerMode;
@@ -123,8 +123,8 @@ namespace StardewModdingAPI.Framework.Models
this.ConsoleColors = consoleColors;
this.SuppressHarmonyDebugMode = suppressHarmonyDebugMode ?? (bool)SConfig.DefaultValues[nameof(this.SuppressHarmonyDebugMode)];
this.SuppressUpdateChecks = new HashSet(suppressUpdateChecks ?? Array.Empty(), StringComparer.OrdinalIgnoreCase);
- this.ModsToLoadEarly = new List(modsToLoadEarly ?? Array.Empty());
- this.ModsToLoadLate = new List(modsToLoadLate ?? Array.Empty());
+ this.ModsToLoadEarly = new HashSet(modsToLoadEarly ?? Array.Empty(), StringComparer.OrdinalIgnoreCase);
+ this.ModsToLoadLate = new HashSet(modsToLoadLate ?? Array.Empty(), StringComparer.OrdinalIgnoreCase);
}
/// Override the value of .
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 4d1eb959..fb835002 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -424,19 +424,27 @@ namespace StardewModdingAPI.Framework
mods = mods.Where(p => !p.IsIgnored).ToArray();
// warn about mods that should load early or late which are not found at all, or both
- foreach (string modId in this.Settings.ModsToLoadEarly)
- if (!mods.Any(m => m.Manifest.UniqueID == modId))
- this.Monitor.Log($" SMAPI configuration specifies a mod {modId} that should load early, but it could not be found or was skipped.", LogLevel.Warn);
- foreach (string modId in this.Settings.ModsToLoadLate)
- if (!mods.Any(m => m.Manifest.UniqueID == modId))
- this.Monitor.Log($" SMAPI configuration specifies a mod {modId} that should load late, but it could not be found or was skipped.", LogLevel.Warn);
- foreach (string modId in this.Settings.ModsToLoadEarly)
- if (this.Settings.ModsToLoadLate.Contains(modId))
- this.Monitor.Log($" SMAPI configuration specifies a mod {modId} that should load both early and late - this will be ignored.", LogLevel.Warn);
+ {
+ HashSet installedIds = new HashSet(mods.Select(p => p.Manifest.UniqueID), StringComparer.OrdinalIgnoreCase);
+
+ foreach (string modId in this.Settings.ModsToLoadEarly)
+ {
+ if (!installedIds.Contains(modId))
+ this.Monitor.Log($" SMAPI configuration specifies a mod {modId} that should load early, but it could not be found or was skipped.", LogLevel.Warn);
+ }
+ foreach (string modId in this.Settings.ModsToLoadLate)
+ {
+ if (this.Settings.ModsToLoadEarly.Contains(modId))
+ this.Monitor.Log($" SMAPI configuration specifies a mod {modId} that should load both early and late - this will be ignored.", LogLevel.Warn);
+ else if (!installedIds.Contains(modId))
+ this.Monitor.Log($" SMAPI configuration specifies a mod {modId} that should load late, but it could not be found or was skipped.", LogLevel.Warn);
+ }
+ }
// load mods
resolver.ValidateManifests(mods, Constants.ApiVersion, toolkit.GetUpdateUrl, getFileLookup: this.GetFileLookup);
- mods = resolver.ProcessDependencies(mods, this.Settings.ModsToLoadEarly, this.Settings.ModsToLoadLate, modDatabase).ToArray();
+ mods = resolver.ApplyLoadOrderOverrides(mods, this.Settings.ModsToLoadEarly, this.Settings.ModsToLoadLate);
+ mods = resolver.ProcessDependencies(mods, modDatabase).ToArray();
this.LoadMods(mods, this.Toolkit.JsonHelper, this.ContentCore, modDatabase);
// check for software likely to cause issues