summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/ModLoading
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework/ModLoading')
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/ModResolver.cs138
3 files changed, 48 insertions, 94 deletions
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
index 01037870..ae08d972 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -221,7 +221,7 @@ namespace StardewModdingAPI.Framework.ModLoading
/// </remarks>
public static Assembly? ResolveAssembly(string name)
{
- string shortName = name.Split(new[] { ',' }, 2).First(); // get simple name (without version and culture)
+ string shortName = name.Split(',', 2).First(); // get simple name (without version and culture)
return AppDomain.CurrentDomain
.GetAssemblies()
.FirstOrDefault(p => p.GetName().Name == shortName);
diff --git a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs
index f5d449c5..4dd9ccc6 100644
--- a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs
+++ b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs
@@ -58,7 +58,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction);
if (methodRef != null && methodRef.DeclaringType.FullName == this.FullTypeName && this.MethodNames.Contains(methodRef.Name))
{
- string eventName = methodRef.Name.Split(new[] { '_' }, 2)[1];
+ string eventName = methodRef.Name.Split('_', 2)[1];
this.MethodNames.Remove($"add_{eventName}");
this.MethodNames.Remove($"remove_{eventName}");
diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs
index fe56f4d2..607bb70d 100644
--- a/src/SMAPI/Framework/ModLoading/ModResolver.cs
+++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs
@@ -4,11 +4,11 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using StardewModdingAPI.Toolkit;
+using StardewModdingAPI.Toolkit.Framework;
using StardewModdingAPI.Toolkit.Framework.ModData;
using StardewModdingAPI.Toolkit.Framework.ModScanning;
using StardewModdingAPI.Toolkit.Framework.UpdateData;
using StardewModdingAPI.Toolkit.Serialization.Models;
-using StardewModdingAPI.Toolkit.Utilities;
using StardewModdingAPI.Toolkit.Utilities.PathLookups;
namespace StardewModdingAPI.Framework.ModLoading
@@ -126,100 +126,23 @@ namespace StardewModdingAPI.Framework.ModLoading
continue;
}
- // validate DLL / content pack fields
+ // validate manifest format
+ if (!ManifestValidator.TryValidateFields(mod.Manifest, out string manifestError))
{
- bool hasDll = !string.IsNullOrWhiteSpace(mod.Manifest.EntryDll);
- bool isContentPack = mod.Manifest.ContentPackFor != null;
-
- // validate field presence
- if (!hasDll && !isContentPack)
- {
- mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest has no {nameof(IManifest.EntryDll)} or {nameof(IManifest.ContentPackFor)} field; must specify one.");
- continue;
- }
- if (hasDll && isContentPack)
- {
- mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest sets both {nameof(IManifest.EntryDll)} and {nameof(IManifest.ContentPackFor)}, which are mutually exclusive.");
- continue;
- }
-
- // validate DLL
- if (hasDll)
- {
- // invalid filename format
- if (mod.Manifest.EntryDll!.Intersect(Path.GetInvalidFileNameChars()).Any())
- {
- mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest has invalid filename '{mod.Manifest.EntryDll}' for the EntryDLL field.");
- continue;
- }
-
- // file doesn't exist
- if (validateFilesExist)
- {
- IFileLookup pathLookup = getFileLookup(mod.DirectoryPath);
- FileInfo file = pathLookup.GetFile(mod.Manifest.EntryDll!);
- if (!file.Exists)
- {
- mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its DLL '{mod.Manifest.EntryDll}' doesn't exist.");
- continue;
- }
- }
- }
-
- // validate content pack
- else
- {
- // invalid content pack ID
- if (string.IsNullOrWhiteSpace(mod.Manifest.ContentPackFor!.UniqueID))
- {
- mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest declares {nameof(IManifest.ContentPackFor)} without its required {nameof(IManifestContentPackFor.UniqueID)} field.");
- continue;
- }
- }
- }
-
- // validate required fields
- {
- List<string> missingFields = new List<string>(3);
-
- if (string.IsNullOrWhiteSpace(mod.Manifest.Name))
- missingFields.Add(nameof(IManifest.Name));
- 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));
-
- if (missingFields.Any())
- {
- mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest is missing required fields ({string.Join(", ", missingFields)}).");
- continue;
- }
+ mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its {manifestError}");
+ continue;
}
- // 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 (IManifestDependency? dependency in mod.Manifest.Dependencies)
+ // check that DLL exists if applicable
+ if (!string.IsNullOrEmpty(mod.Manifest.EntryDll) && validateFilesExist)
{
- // 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))
+ IFileLookup pathLookup = getFileLookup(mod.DirectoryPath);
+ FileInfo file = pathLookup.GetFile(mod.Manifest.EntryDll!);
+ if (!file.Exists)
{
- mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest has a {nameof(IManifest.Dependencies)} entry with no {nameof(IManifestDependency.UniqueID)} field.");
+ mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its DLL '{mod.Manifest.EntryDll}' doesn't exist.");
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).");
}
}
@@ -242,10 +165,41 @@ namespace StardewModdingAPI.Framework.ModLoading
}
}
+ /// <summary>Apply preliminary overrides to the load order based on the SMAPI configuration.</summary>
+ /// <param name="mods">The mods to process.</param>
+ /// <param name="modIdsToLoadEarly">The mod IDs SMAPI should load before any other mods (except those needed to load them).</param>
+ /// <param name="modIdsToLoadLate">The mod IDs SMAPI should load after any other mods.</param>
+ public IModMetadata[] ApplyLoadOrderOverrides(IModMetadata[] mods, HashSet<string> modIdsToLoadEarly, HashSet<string> modIdsToLoadLate)
+ {
+ if (!modIdsToLoadEarly.Any() && !modIdsToLoadLate.Any())
+ return mods;
+
+ string[] earlyArray = modIdsToLoadEarly.ToArray();
+ string[] lateArray = modIdsToLoadLate.ToArray();
+
+ return mods
+ .OrderBy(mod =>
+ {
+ string? id = mod.Manifest?.UniqueID;
+
+ if (id is not null)
+ {
+ if (modIdsToLoadEarly.TryGetValue(id, out string? actualId))
+ return -int.MaxValue + Array.IndexOf(earlyArray, actualId);
+
+ if (modIdsToLoadLate.TryGetValue(id, out actualId))
+ return int.MaxValue - Array.IndexOf(lateArray, actualId);
+ }
+
+ return 0;
+ })
+ .ToArray();
+ }
+
/// <summary>Sort the given mods by the order they should be loaded.</summary>
/// <param name="mods">The mods to process.</param>
/// <param name="modDatabase">Handles access to SMAPI's internal mod metadata list.</param>
- public IEnumerable<IModMetadata> ProcessDependencies(IEnumerable<IModMetadata> mods, ModDatabase modDatabase)
+ public IEnumerable<IModMetadata> ProcessDependencies(IReadOnlyList<IModMetadata> mods, ModDatabase modDatabase)
{
// initialize metadata
mods = mods.ToArray();
@@ -261,7 +215,7 @@ namespace StardewModdingAPI.Framework.ModLoading
// sort mods
foreach (IModMetadata mod in mods)
- this.ProcessDependencies(mods.ToArray(), modDatabase, mod, states, sortedMods, new List<IModMetadata>());
+ this.ProcessDependencies(mods, modDatabase, mod, states, sortedMods, new List<IModMetadata>());
return sortedMods.Reverse();
}
@@ -278,7 +232,7 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <param name="sortedMods">The list in which to save mods sorted by dependency order.</param>
/// <param name="currentChain">The current change of mod dependencies.</param>
/// <returns>Returns the mod dependency status.</returns>
- private ModDependencyStatus ProcessDependencies(IModMetadata[] mods, ModDatabase modDatabase, IModMetadata mod, IDictionary<IModMetadata, ModDependencyStatus> states, Stack<IModMetadata> sortedMods, ICollection<IModMetadata> currentChain)
+ private ModDependencyStatus ProcessDependencies(IReadOnlyList<IModMetadata> mods, ModDatabase modDatabase, IModMetadata mod, IDictionary<IModMetadata, ModDependencyStatus> states, Stack<IModMetadata> sortedMods, ICollection<IModMetadata> currentChain)
{
// check if already visited
switch (states[mod])
@@ -409,7 +363,7 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <summary>Get the dependencies declared in a manifest.</summary>
/// <param name="manifest">The mod manifest.</param>
/// <param name="loadedMods">The loaded mods.</param>
- private IEnumerable<ModDependency> GetDependenciesFrom(IManifest manifest, IModMetadata[] loadedMods)
+ private IEnumerable<ModDependency> GetDependenciesFrom(IManifest manifest, IReadOnlyList<IModMetadata> loadedMods)
{
IModMetadata? FindMod(string id) => loadedMods.FirstOrDefault(m => m.HasID(id));