diff --git a/src/SMAPI.ModBuildConfig/DeployModTask.cs b/src/SMAPI.ModBuildConfig/DeployModTask.cs
index 1581b282..3508a6db 100644
--- a/src/SMAPI.ModBuildConfig/DeployModTask.cs
+++ b/src/SMAPI.ModBuildConfig/DeployModTask.cs
@@ -85,33 +85,30 @@ namespace StardewModdingAPI.ModBuildConfig
return true;
// validate the manifest file
- Manifest manifest;
+ IManifest manifest;
{
- // check if manifest file exists
- FileInfo manifestFile = new(Path.Combine(this.ProjectDir, "manifest.json"));
- if (!manifestFile.Exists)
- {
- this.Log.LogError("[mod build package] The mod does not have a manifest.json file.");
- return false;
- }
-
- // check if the json is valid
try
{
- new JsonHelper().ReadJsonFileIfExists(manifestFile.FullName, out manifest);
+ string manifestPath = Path.Combine(this.ProjectDir, "manifest.json");
+ if (!new JsonHelper().ReadJsonFileIfExists(manifestPath, out Manifest rawManifest))
+ {
+ this.Log.LogError("[mod build package] The mod's manifest.json file doesn't exist.");
+ return false;
+ }
+ manifest = rawManifest;
}
catch (JsonReaderException ex)
{
// log the inner exception, otherwise the message will be generic
Exception exToShow = ex.InnerException ?? ex;
- this.Log.LogError($"[mod build package] Failed to parse manifest.json: {exToShow.Message}");
+ this.Log.LogError($"[mod build package] The mod's manifest.json file isn't valid JSON: {exToShow.Message}");
return false;
}
- // validate the manifest's fields
- if (!ManifestValidator.TryValidate(manifest, out string error))
+ // validate manifest fields
+ if (!ManifestValidator.TryValidateFields(manifest, out string error))
{
- this.Log.LogError($"[mod build package] The mod manifest is invalid: {error}");
+ this.Log.LogError($"[mod build package] The mod's manifest.json file is invalid: {error}");
return false;
}
}
diff --git a/src/SMAPI.Toolkit/Framework/ManifestValidator.cs b/src/SMAPI.Toolkit/Framework/ManifestValidator.cs
index 62cfd8e9..461dc325 100644
--- a/src/SMAPI.Toolkit/Framework/ManifestValidator.cs
+++ b/src/SMAPI.Toolkit/Framework/ManifestValidator.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using StardewModdingAPI.Toolkit.Utilities;
@@ -8,40 +9,47 @@ namespace StardewModdingAPI.Toolkit.Framework
/// Validates manifest fields.
public static class ManifestValidator
{
- /// Try to validate a manifest's fields. Fails if any invalid field is found.
+ /// Validate a manifest's fields.
/// The manifest to validate.
- /// The error message to display to the user.
- /// Returns whether the manifest was validated successfully.
- public static bool TryValidate(IManifest manifest, out string error)
+ /// The error message indicating why validation failed, if applicable.
+ /// Returns whether all manifest fields validated successfully.
+ [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract", Justification = "This is the method that ensures those annotations are respected.")]
+ public static bool TryValidateFields(IManifest manifest, out string error)
{
- // validate DLL / content pack fields
+ //
+ // Note: SMAPI assumes that it can grammatically append the returned sentence in the
+ // form "failed loading because its ". Any errors returned should be valid
+ // in that format, unless the SMAPI call is adjusted accordingly.
+ //
+
bool hasDll = !string.IsNullOrWhiteSpace(manifest.EntryDll);
bool isContentPack = manifest.ContentPackFor != null;
- // validate field presence
- if (!hasDll && !isContentPack)
+ // validate use of EntryDll vs ContentPackFor fields
+ if (hasDll == isContentPack)
{
- error = $"manifest has no {nameof(IManifest.EntryDll)} or {nameof(IManifest.ContentPackFor)} field; must specify one.";
- return false;
- }
- if (hasDll && isContentPack)
- {
- error = $"manifest sets both {nameof(IManifest.EntryDll)} and {nameof(IManifest.ContentPackFor)}, which are mutually exclusive.";
+ error = hasDll
+ ? $"manifest sets both {nameof(IManifest.EntryDll)} and {nameof(IManifest.ContentPackFor)}, which are mutually exclusive."
+ : $"manifest has no {nameof(IManifest.EntryDll)} or {nameof(IManifest.ContentPackFor)} field; must specify one.";
return false;
}
- // validate DLL filename format
- if (hasDll && manifest.EntryDll!.Intersect(Path.GetInvalidFileNameChars()).Any())
+ // validate EntryDll/ContentPackFor format
+ if (hasDll)
{
- error = $"manifest has invalid filename '{manifest.EntryDll}' for the EntryDLL field.";
- return false;
+ if (manifest.EntryDll!.Intersect(Path.GetInvalidFileNameChars()).Any())
+ {
+ error = $"manifest has invalid filename '{manifest.EntryDll}' for the {nameof(IManifest.EntryDll)} field.";
+ return false;
+ }
}
-
- // validate content pack ID
- else if (isContentPack && string.IsNullOrWhiteSpace(manifest.ContentPackFor!.UniqueID))
+ else
{
- error = $"manifest declares {nameof(IManifest.ContentPackFor)} without its required {nameof(IManifestContentPackFor.UniqueID)} field.";
- return false;
+ if (string.IsNullOrWhiteSpace(manifest.ContentPackFor!.UniqueID))
+ {
+ error = $"manifest declares {nameof(IManifest.ContentPackFor)} without its required {nameof(IManifestContentPackFor.UniqueID)} field.";
+ return false;
+ }
}
// validate required fields
@@ -69,24 +77,21 @@ namespace StardewModdingAPI.Toolkit.Framework
return false;
}
- // validate dependencies
+ // validate dependency format
foreach (IManifestDependency? dependency in manifest.Dependencies)
{
- // null dependency
if (dependency == null)
{
error = $"manifest has a null entry under {nameof(IManifest.Dependencies)}.";
return false;
}
- // missing ID
if (string.IsNullOrWhiteSpace(dependency.UniqueID))
{
error = $"manifest has a {nameof(IManifest.Dependencies)} entry with no {nameof(IManifestDependency.UniqueID)} field.";
return false;
}
- // invalid ID
if (!PathUtilities.IsSlug(dependency.UniqueID))
{
error = $"manifest has a {nameof(IManifest.Dependencies)} entry with an invalid {nameof(IManifestDependency.UniqueID)} field (IDs must only contain letters, numbers, underscores, periods, or hyphens).";
diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs
index 0b4fe3e9..9db9db99 100644
--- a/src/SMAPI/Framework/ModLoading/ModResolver.cs
+++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs
@@ -126,7 +126,14 @@ namespace StardewModdingAPI.Framework.ModLoading
continue;
}
- // check for dll if it's supposed to have one
+ // validate manifest format
+ if (!ManifestValidator.TryValidateFields(mod.Manifest, out string manifestError))
+ {
+ mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its {manifestError}");
+ continue;
+ }
+
+ // check that DLL exists if applicable
if (!string.IsNullOrEmpty(mod.Manifest.EntryDll) && validateFilesExist)
{
IFileLookup pathLookup = getFileLookup(mod.DirectoryPath);
@@ -137,13 +144,6 @@ namespace StardewModdingAPI.Framework.ModLoading
continue;
}
}
-
- // validate manifest
- if (!ManifestValidator.TryValidate(mod.Manifest, out string manifestError))
- {
- mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its {manifestError}");
- continue;
- }
}
// validate IDs are unique