summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/release-notes.md4
-rw-r--r--src/SMAPI.Tests/Core/ModResolverTests.cs14
-rw-r--r--src/SMAPI/Framework/IModMetadata.cs10
-rw-r--r--src/SMAPI/Framework/ModLoading/ModMetadata.cs25
-rw-r--r--src/SMAPI/Framework/ModLoading/ModResolver.cs12
-rw-r--r--src/SMAPI/Framework/SCore.cs10
6 files changed, 54 insertions, 21 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 9c3e3d28..d6076b0f 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -12,6 +12,7 @@ These changes have not been released yet.
* Now ignores metadata files/folders like `__MACOSX` and `__folder_managed_by_vortex`.
* Now ignores content files like `.txt` or `.png`, which avoids missing-manifest errors in some common cases.
* Now detects XNB mods more accurately, and consolidates multi-folder XNB mods.
+ * Duplicate-mod errors now show the mod version in each folder.
* Updated mod compatibility list.
* Fixed mods needing to load custom `Map` assets before the game accesses them (SMAPI will now do so automatically).
* Fixed Save Backup not pruning old backups if they're uncompressed.
@@ -37,7 +38,8 @@ These changes have not been released yet.
* Added separate `LogNetworkTraffic` option to make verbose logging less overwhelmingly verbose.
* Added asset propagation for critter textures and `DayTimeMoneyBox` buttons.
* The installer now recognises custom game paths stored in `stardewvalley.targets`, if any.
- * When a mod is incompatible, the trace logs now list all detected issues instead of the first one.
+ * Trace logs for a broken mod now list all detected issues (instead of the first one).
+ * Trace logs when loading mods are now more clear.
* Removed all deprecated APIs.
* Removed the `Monitor.ExitGameImmediately` method.
* Updated dependencies (including Json.NET 11.0.2 → 12.0.2, Mono.Cecil 0.10.1 → 0.10.4).
diff --git a/src/SMAPI.Tests/Core/ModResolverTests.cs b/src/SMAPI.Tests/Core/ModResolverTests.cs
index 4a1f04c6..ee1c0b99 100644
--- a/src/SMAPI.Tests/Core/ModResolverTests.cs
+++ b/src/SMAPI.Tests/Core/ModResolverTests.cs
@@ -27,7 +27,7 @@ namespace StardewModdingAPI.Tests.Core
public void ReadBasicManifest_NoMods_ReturnsEmptyList()
{
// arrange
- string rootFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
+ string rootFolder = this.GetTempFolderPath();
Directory.CreateDirectory(rootFolder);
// act
@@ -41,7 +41,7 @@ namespace StardewModdingAPI.Tests.Core
public void ReadBasicManifest_EmptyModFolder_ReturnsFailedManifest()
{
// arrange
- string rootFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
+ string rootFolder = this.GetTempFolderPath();
string modFolder = Path.Combine(rootFolder, Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(modFolder);
@@ -78,7 +78,7 @@ namespace StardewModdingAPI.Tests.Core
};
// write to filesystem
- string rootFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
+ string rootFolder = this.GetTempFolderPath();
string modFolder = Path.Combine(rootFolder, Guid.NewGuid().ToString("N"));
string filename = Path.Combine(modFolder, "manifest.json");
Directory.CreateDirectory(modFolder);
@@ -209,7 +209,7 @@ namespace StardewModdingAPI.Tests.Core
IManifest manifest = this.GetManifest();
// create DLL
- string modFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
+ string modFolder = Path.Combine(this.GetTempFolderPath(), Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(modFolder);
File.WriteAllText(Path.Combine(modFolder, manifest.EntryDll), "");
@@ -462,6 +462,12 @@ namespace StardewModdingAPI.Tests.Core
/*********
** Private methods
*********/
+ /// <summary>Get a generated folder path in the temp folder. This folder isn't created automatically.</summary>
+ private string GetTempFolderPath()
+ {
+ return Path.Combine(Path.GetTempPath(), "smapi-unit-tests", Guid.NewGuid().ToString("N"));
+ }
+
/// <summary>Get a randomised basic manifest.</summary>
/// <param name="id">The <see cref="IManifest.UniqueID"/> value, or <c>null</c> for a generated value.</param>
/// <param name="name">The <see cref="IManifest.Name"/> value, or <c>null</c> for a generated value.</param>
diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs
index 32870c2a..6ee7df69 100644
--- a/src/SMAPI/Framework/IModMetadata.cs
+++ b/src/SMAPI/Framework/IModMetadata.cs
@@ -16,10 +16,13 @@ namespace StardewModdingAPI.Framework
/// <summary>The mod's display name.</summary>
string DisplayName { get; }
- /// <summary>The mod's full directory path.</summary>
+ /// <summary>The root path containing mods.</summary>
+ string RootPath { get; }
+
+ /// <summary>The mod's full directory path within the <see cref="RootPath"/>.</summary>
string DirectoryPath { get; }
- /// <summary>The <see cref="DirectoryPath"/> relative to the game's Mods folder.</summary>
+ /// <summary>The <see cref="DirectoryPath"/> relative to the <see cref="RootPath"/>.</summary>
string RelativeDirectoryPath { get; }
/// <summary>Metadata about the mod from SMAPI's internal data (if any).</summary>
@@ -108,5 +111,8 @@ namespace StardewModdingAPI.Framework
/// <summary>Get whether the mod has a given warning and it hasn't been suppressed in the <see cref="DataRecord"/>.</summary>
/// <param name="warning">The warning to check.</param>
bool HasUnsuppressWarning(ModWarning warning);
+
+ /// <summary>Get a relative path which includes the root folder name.</summary>
+ string GetRelativePathWithRoot();
}
}
diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs
index 39f2f482..7f788d17 100644
--- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs
+++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using StardewModdingAPI.Framework.ModHelpers;
using StardewModdingAPI.Toolkit.Framework.Clients.WebApi;
using StardewModdingAPI.Toolkit.Framework.ModData;
using StardewModdingAPI.Toolkit.Framework.UpdateData;
+using StardewModdingAPI.Toolkit.Utilities;
namespace StardewModdingAPI.Framework.ModLoading
{
@@ -17,10 +19,13 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <summary>The mod's display name.</summary>
public string DisplayName { get; }
- /// <summary>The mod's full directory path.</summary>
+ /// <summary>The root path containing mods.</summary>
+ public string RootPath { get; }
+
+ /// <summary>The mod's full directory path within the <see cref="RootPath"/>.</summary>
public string DirectoryPath { get; }
- /// <summary>The <see cref="IModMetadata.DirectoryPath"/> relative to the game's Mods folder.</summary>
+ /// <summary>The <see cref="DirectoryPath"/> relative to the <see cref="RootPath"/>.</summary>
public string RelativeDirectoryPath { get; }
/// <summary>The mod manifest.</summary>
@@ -68,16 +73,17 @@ namespace StardewModdingAPI.Framework.ModLoading
*********/
/// <summary>Construct an instance.</summary>
/// <param name="displayName">The mod's display name.</param>
- /// <param name="directoryPath">The mod's full directory path.</param>
- /// <param name="relativeDirectoryPath">The <paramref name="directoryPath"/> relative to the game's Mods folder.</param>
+ /// <param name="directoryPath">The mod's full directory path within the <paramref name="rootPath"/>.</param>
+ /// <param name="rootPath">The root path containing mods.</param>
/// <param name="manifest">The mod manifest.</param>
/// <param name="dataRecord">Metadata about the mod from SMAPI's internal data (if any).</param>
/// <param name="isIgnored">Whether the mod folder should be ignored. This should be <c>true</c> if it was found within a folder whose name starts with a dot.</param>
- public ModMetadata(string displayName, string directoryPath, string relativeDirectoryPath, IManifest manifest, ModDataRecordVersionedFields dataRecord, bool isIgnored)
+ public ModMetadata(string displayName, string directoryPath, string rootPath, IManifest manifest, ModDataRecordVersionedFields dataRecord, bool isIgnored)
{
this.DisplayName = displayName;
this.DirectoryPath = directoryPath;
- this.RelativeDirectoryPath = relativeDirectoryPath;
+ this.RootPath = rootPath;
+ this.RelativeDirectoryPath = PathUtilities.GetRelativePath(this.RootPath, this.DirectoryPath);
this.Manifest = manifest;
this.DataRecord = dataRecord;
this.IsIgnored = isIgnored;
@@ -196,5 +202,12 @@ namespace StardewModdingAPI.Framework.ModLoading
this.Warnings.HasFlag(warning)
&& (this.DataRecord?.DataRecord == null || !this.DataRecord.DataRecord.SuppressWarnings.HasFlag(warning));
}
+
+ /// <summary>Get a relative path which includes the root folder name.</summary>
+ public string GetRelativePathWithRoot()
+ {
+ string rootFolderName = Path.GetFileName(this.RootPath) ?? "";
+ return Path.Combine(rootFolderName, this.RelativeDirectoryPath);
+ }
}
}
diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs
index b6bdb357..20941a5f 100644
--- a/src/SMAPI/Framework/ModLoading/ModResolver.cs
+++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs
@@ -42,9 +42,8 @@ namespace StardewModdingAPI.Framework.ModLoading
ModMetadataStatus status = folder.ManifestParseError == ModParseError.None || shouldIgnore
? ModMetadataStatus.Found
: ModMetadataStatus.Failed;
- string relativePath = PathUtilities.GetRelativePath(rootPath, folder.Directory.FullName);
- yield return new ModMetadata(folder.DisplayName, folder.Directory.FullName, relativePath, manifest, dataRecord, isIgnored: shouldIgnore)
+ yield return new ModMetadata(folder.DisplayName, folder.Directory.FullName, rootPath, manifest, dataRecord, isIgnored: shouldIgnore)
.SetStatus(status, shouldIgnore ? "disabled by dot convention" : folder.ManifestParseErrorText);
}
}
@@ -199,7 +198,14 @@ namespace StardewModdingAPI.Framework.ModLoading
{
if (mod.Status == ModMetadataStatus.Failed)
continue; // don't replace metadata error
- mod.SetStatus(ModMetadataStatus.Failed, $"you have multiple copies of this mod installed ({string.Join(", ", group.Select(p => p.RelativeDirectoryPath).OrderBy(p => p))}).");
+
+ string folderList = string.Join(", ",
+ from entry in @group
+ let relativePath = entry.GetRelativePathWithRoot()
+ orderby relativePath
+ select $"{relativePath} ({entry.Manifest.Version})"
+ );
+ mod.SetStatus(ModMetadataStatus.Failed, $"you have multiple copies of this mod installed. Found in folders: {folderList}.");
}
}
}
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 60deed70..0aae3b84 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -400,7 +400,7 @@ namespace StardewModdingAPI.Framework
// filter out ignored mods
foreach (IModMetadata mod in mods.Where(p => p.IsIgnored))
- this.Monitor.Log($" Skipped {mod.RelativeDirectoryPath} (folder name starts with a dot).", LogLevel.Trace);
+ this.Monitor.Log($" Skipped {mod.GetRelativePathWithRoot()} (folder name starts with a dot).", LogLevel.Trace);
mods = mods.Where(p => !p.IsIgnored).ToArray();
// load mods
@@ -902,13 +902,13 @@ namespace StardewModdingAPI.Framework
// log entry
{
- string relativePath = PathUtilities.GetRelativePath(this.ModsPath, mod.DirectoryPath);
+ string relativePath = mod.GetRelativePathWithRoot();
if (mod.IsContentPack)
- this.Monitor.Log($" {mod.DisplayName} ({relativePath}) [content pack]...", LogLevel.Trace);
+ this.Monitor.Log($" {mod.DisplayName} (from {relativePath}) [content pack]...", LogLevel.Trace);
else if (mod.Manifest?.EntryDll != null)
- this.Monitor.Log($" {mod.DisplayName} ({relativePath}{Path.DirectorySeparatorChar}{mod.Manifest.EntryDll})...", LogLevel.Trace); // don't use Path.Combine here, since EntryDLL might not be valid
+ this.Monitor.Log($" {mod.DisplayName} (from {relativePath}{Path.DirectorySeparatorChar}{mod.Manifest.EntryDll})...", LogLevel.Trace); // don't use Path.Combine here, since EntryDLL might not be valid
else
- this.Monitor.Log($" {mod.DisplayName} ({relativePath})...", LogLevel.Trace);
+ this.Monitor.Log($" {mod.DisplayName} (from {relativePath})...", LogLevel.Trace);
}
// add warning for missing update key