summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-05-06 20:58:19 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-05-06 20:58:19 -0400
commite7e6327b3c85d52ab666aad2a054fbbdbd9431da (patch)
tree436b963d47bd8062ab38b676f57cbfb5c4e99821
parentc8ad50dad1d706a1901798f9396f6becfea36c0e (diff)
parentb45f50b57e3895620a682e2c7a438391dce0c5dd (diff)
downloadSMAPI-e7e6327b3c85d52ab666aad2a054fbbdbd9431da.tar.gz
SMAPI-e7e6327b3c85d52ab666aad2a054fbbdbd9431da.tar.bz2
SMAPI-e7e6327b3c85d52ab666aad2a054fbbdbd9431da.zip
Merge branch 'develop' into stable
-rw-r--r--build/common.targets2
-rw-r--r--docs/release-notes.md13
-rw-r--r--src/SMAPI.Installer/InteractiveInstaller.cs4
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/manifest.json4
-rw-r--r--src/SMAPI.Mods.ErrorHandler/manifest.json4
-rw-r--r--src/SMAPI.Mods.SaveBackup/manifest.json4
-rw-r--r--src/SMAPI.Tests/Core/ModResolverTests.cs21
-rw-r--r--src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs18
-rw-r--r--src/SMAPI.Toolkit/ModToolkit.cs10
-rw-r--r--src/SMAPI.Toolkit/Serialization/JsonHelper.cs5
-rw-r--r--src/SMAPI.Toolkit/Utilities/PathLookups/CaseInsensitivePathLookup.cs (renamed from src/SMAPI.Toolkit/Utilities/CaseInsensitivePathLookup.cs)17
-rw-r--r--src/SMAPI.Toolkit/Utilities/PathLookups/IFilePathLookup.cs20
-rw-r--r--src/SMAPI.Toolkit/Utilities/PathLookups/MinimalPathLookup.cs31
-rw-r--r--src/SMAPI.Web/wwwroot/SMAPI.metadata.json9
-rw-r--r--src/SMAPI/Constants.cs2
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs124
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs65
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs5
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs4
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs16
-rw-r--r--src/SMAPI/Framework/ContentPack.cs9
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModContentHelper.cs18
-rw-r--r--src/SMAPI/Framework/ModLoading/ModResolver.cs12
-rw-r--r--src/SMAPI/Framework/Models/SConfig.cs18
-rw-r--r--src/SMAPI/Framework/SCore.cs26
-rw-r--r--src/SMAPI/Framework/SMultiplayer.cs29
-rw-r--r--src/SMAPI/Metadata/CoreAssetPropagator.cs51
-rw-r--r--src/SMAPI/SMAPI.config.json7
28 files changed, 339 insertions, 209 deletions
diff --git a/build/common.targets b/build/common.targets
index a8dda9e0..44ee0caf 100644
--- a/build/common.targets
+++ b/build/common.targets
@@ -1,7 +1,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!--set general build properties -->
- <Version>3.14.0</Version>
+ <Version>3.14.1</Version>
<Product>SMAPI</Product>
<LangVersion>latest</LangVersion>
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 499c1d9b..82cf51db 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,6 +1,19 @@
← [README](README.md)
# Release notes
+## 3.14.1
+Released 06 May 2022 for Stardew Valley 1.5.6 or later.
+
+* For players:
+ * Improved performance for mods still using the previous content API.
+ * Disabled case-insensitive file paths (introduced in 3.14.0) by default.
+ _You can enable them by editing `smapi-internal/config.json` if needed. They'll be re-enabled in an upcoming version after they're reworked a bit._
+ * Removed experimental 'aggressive memory optimizations' option.
+ _This was disabled by default and is no longer needed in most cases. Memory usage will be better reduced by reworked asset propagation in the upcoming SMAPI 4.0.0._
+ * Fixed 'content file was not found' error when the game tries to load unlocalized text from a localizable mod data asset in 3.14.0.
+ * Fixed error reading empty JSON files. These are now treated as if they didn't exist (matching pre-3.14.0 behavior).
+ * Updated compatibility list.
+
## 3.14.0
Released 01 May 2022 for Stardew Valley 1.5.6 or later. See [release highlights](https://www.patreon.com/posts/65265507).
diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs
index 5138173a..19cefd32 100644
--- a/src/SMAPI.Installer/InteractiveInstaller.cs
+++ b/src/SMAPI.Installer/InteractiveInstaller.cs
@@ -435,8 +435,8 @@ namespace StardewModdingApi.Installer
{
this.PrintDebug("Adding bundled mods...");
- ModFolder[] targetMods = toolkit.GetModFolders(paths.ModsPath).ToArray();
- foreach (ModFolder sourceMod in toolkit.GetModFolders(bundledModsDir.FullName))
+ ModFolder[] targetMods = toolkit.GetModFolders(paths.ModsPath, useCaseInsensitiveFilePaths: true).ToArray();
+ foreach (ModFolder sourceMod in toolkit.GetModFolders(bundledModsDir.FullName, useCaseInsensitiveFilePaths: true))
{
// validate source mod
if (sourceMod.Manifest == null)
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json
index 38945c5d..edbdd081 100644
--- a/src/SMAPI.Mods.ConsoleCommands/manifest.json
+++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Console Commands",
"Author": "SMAPI",
- "Version": "3.14.0",
+ "Version": "3.14.1",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
- "MinimumApiVersion": "3.14.0"
+ "MinimumApiVersion": "3.14.1"
}
diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json
index c082bf75..af67453d 100644
--- a/src/SMAPI.Mods.ErrorHandler/manifest.json
+++ b/src/SMAPI.Mods.ErrorHandler/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Error Handler",
"Author": "SMAPI",
- "Version": "3.14.0",
+ "Version": "3.14.1",
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
"UniqueID": "SMAPI.ErrorHandler",
"EntryDll": "ErrorHandler.dll",
- "MinimumApiVersion": "3.14.0"
+ "MinimumApiVersion": "3.14.1"
}
diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json
index 28bffa98..14b68cb4 100644
--- a/src/SMAPI.Mods.SaveBackup/manifest.json
+++ b/src/SMAPI.Mods.SaveBackup/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Save Backup",
"Author": "SMAPI",
- "Version": "3.14.0",
+ "Version": "3.14.1",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
- "MinimumApiVersion": "3.14.0"
+ "MinimumApiVersion": "3.14.1"
}
diff --git a/src/SMAPI.Tests/Core/ModResolverTests.cs b/src/SMAPI.Tests/Core/ModResolverTests.cs
index 6b2746f5..3dfc9461 100644
--- a/src/SMAPI.Tests/Core/ModResolverTests.cs
+++ b/src/SMAPI.Tests/Core/ModResolverTests.cs
@@ -12,6 +12,7 @@ using StardewModdingAPI.Toolkit;
using StardewModdingAPI.Toolkit.Framework.ModData;
using StardewModdingAPI.Toolkit.Framework.UpdateData;
using StardewModdingAPI.Toolkit.Serialization.Models;
+using StardewModdingAPI.Toolkit.Utilities.PathLookups;
using SemanticVersion = StardewModdingAPI.SemanticVersion;
namespace SMAPI.Tests.Core
@@ -34,7 +35,7 @@ namespace SMAPI.Tests.Core
Directory.CreateDirectory(rootFolder);
// act
- IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase()).ToArray();
+ IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase(), useCaseInsensitiveFilePaths: true).ToArray();
// assert
Assert.AreEqual(0, mods.Length, 0, $"Expected to find zero manifests, found {mods.Length} instead.");
@@ -52,7 +53,7 @@ namespace SMAPI.Tests.Core
Directory.CreateDirectory(modFolder);
// act
- IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase()).ToArray();
+ IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase(), useCaseInsensitiveFilePaths: true).ToArray();
IModMetadata? mod = mods.FirstOrDefault();
// assert
@@ -94,7 +95,7 @@ namespace SMAPI.Tests.Core
File.WriteAllText(filename, JsonConvert.SerializeObject(original));
// act
- IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase()).ToArray();
+ IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase(), useCaseInsensitiveFilePaths: true).ToArray();
IModMetadata? mod = mods.FirstOrDefault();
// assert
@@ -132,7 +133,7 @@ namespace SMAPI.Tests.Core
[Test(Description = "Assert that validation doesn't fail if there are no mods installed.")]
public void ValidateManifests_NoMods_DoesNothing()
{
- new ModResolver().ValidateManifests(Array.Empty<ModMetadata>(), apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null, validateFilesExist: false);
+ new ModResolver().ValidateManifests(Array.Empty<ModMetadata>(), apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null, getFilePathLookup: _ => MinimalPathLookup.Instance, validateFilesExist: false);
}
[Test(Description = "Assert that validation skips manifests that have already failed without calling any other properties.")]
@@ -143,7 +144,7 @@ namespace SMAPI.Tests.Core
mock.Setup(p => p.Status).Returns(ModMetadataStatus.Failed);
// act
- new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null, validateFilesExist: false);
+ new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null, getFilePathLookup: _ => MinimalPathLookup.Instance, validateFilesExist: false);
// assert
mock.VerifyGet(p => p.Status, Times.Once, "The validation did not check the manifest status.");
@@ -160,7 +161,7 @@ namespace SMAPI.Tests.Core
});
// act
- new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null, validateFilesExist: false);
+ new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null, getFilePathLookup: _ => MinimalPathLookup.Instance, validateFilesExist: false);
// assert
mock.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "The validation did not fail the metadata.");
@@ -174,7 +175,7 @@ namespace SMAPI.Tests.Core
mock.Setup(p => p.Manifest).Returns(this.GetManifest(minimumApiVersion: "1.1"));
// act
- new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null, validateFilesExist: false);
+ new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null, getFilePathLookup: _ => MinimalPathLookup.Instance, validateFilesExist: false);
// assert
mock.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "The validation did not fail the metadata.");
@@ -189,7 +190,7 @@ namespace SMAPI.Tests.Core
Directory.CreateDirectory(directoryPath);
// act
- new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null);
+ new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null, getFilePathLookup: _ => MinimalPathLookup.Instance);
// assert
mock.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "The validation did not fail the metadata.");
@@ -206,7 +207,7 @@ namespace SMAPI.Tests.Core
Mock<IModMetadata> modB = this.GetMetadata(this.GetManifest(id: "Mod A", name: "Mod B", version: "1.0"), allowStatusChange: true);
// act
- new ModResolver().ValidateManifests(new[] { modA.Object, modB.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null, validateFilesExist: false);
+ new ModResolver().ValidateManifests(new[] { modA.Object, modB.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null, getFilePathLookup: _ => MinimalPathLookup.Instance, validateFilesExist: false);
// assert
modA.Verify(p => p.SetStatus(ModMetadataStatus.Failed, ModFailReason.Duplicate, It.IsAny<string>(), It.IsAny<string>()), Times.AtLeastOnce, "The validation did not fail the first mod with a unique ID.");
@@ -232,7 +233,7 @@ namespace SMAPI.Tests.Core
mock.Setup(p => p.DirectoryPath).Returns(modFolder);
// act
- new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null);
+ new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null, getFilePathLookup: _ => MinimalPathLookup.Instance);
// assert
// if Moq doesn't throw a method-not-setup exception, the validation didn't override the status.
diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs
index 24485620..aa4c3338 100644
--- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs
+++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs
@@ -5,7 +5,7 @@ using System.Linq;
using System.Text.RegularExpressions;
using StardewModdingAPI.Toolkit.Serialization;
using StardewModdingAPI.Toolkit.Serialization.Models;
-using StardewModdingAPI.Toolkit.Utilities;
+using StardewModdingAPI.Toolkit.Utilities.PathLookups;
namespace StardewModdingAPI.Toolkit.Framework.ModScanning
{
@@ -95,19 +95,20 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
/// <summary>Extract information about all mods in the given folder.</summary>
/// <param name="rootPath">The root folder containing mods.</param>
- public IEnumerable<ModFolder> GetModFolders(string rootPath)
+ /// <param name="useCaseInsensitiveFilePaths">Whether to match file paths case-insensitively, even on Linux.</param>
+ public IEnumerable<ModFolder> GetModFolders(string rootPath, bool useCaseInsensitiveFilePaths)
{
DirectoryInfo root = new(rootPath);
- return this.GetModFolders(root, root);
+ return this.GetModFolders(root, root, useCaseInsensitiveFilePaths);
}
/// <summary>Extract information about all mods in the given folder.</summary>
/// <param name="rootPath">The root folder containing mods. Only the <paramref name="modPath"/> will be searched, but this field allows it to be treated as a potential mod folder of its own.</param>
/// <param name="modPath">The mod path to search.</param>
- // /// <param name="tryConsolidateMod">If the folder contains multiple XNB mods, treat them as subfolders of a single mod. This is useful when reading a single mod archive, as opposed to a mods folder.</param>
- public IEnumerable<ModFolder> GetModFolders(string rootPath, string modPath)
+ /// <param name="useCaseInsensitiveFilePaths">Whether to match file paths case-insensitively, even on Linux.</param>
+ public IEnumerable<ModFolder> GetModFolders(string rootPath, string modPath, bool useCaseInsensitiveFilePaths)
{
- return this.GetModFolders(root: new DirectoryInfo(rootPath), folder: new DirectoryInfo(modPath));
+ return this.GetModFolders(root: new DirectoryInfo(rootPath), folder: new DirectoryInfo(modPath), useCaseInsensitiveFilePaths: useCaseInsensitiveFilePaths);
}
/// <summary>Extract information from a mod folder.</summary>
@@ -195,7 +196,8 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
/// <summary>Recursively extract information about all mods in the given folder.</summary>
/// <param name="root">The root mod folder.</param>
/// <param name="folder">The folder to search for mods.</param>
- private IEnumerable<ModFolder> GetModFolders(DirectoryInfo root, DirectoryInfo folder)
+ /// <param name="useCaseInsensitiveFilePaths">Whether to match file paths case-insensitively, even on Linux.</param>
+ private IEnumerable<ModFolder> GetModFolders(DirectoryInfo root, DirectoryInfo folder, bool useCaseInsensitiveFilePaths)
{
bool isRoot = folder.FullName == root.FullName;
@@ -214,7 +216,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
// find mods in subfolders
if (this.IsModSearchFolder(root, folder))
{
- IEnumerable<ModFolder> subfolders = folder.EnumerateDirectories().SelectMany(sub => this.GetModFolders(root, sub));
+ IEnumerable<ModFolder> subfolders = folder.EnumerateDirectories().SelectMany(sub => this.GetModFolders(root, sub, useCaseInsensitiveFilePaths));
if (!isRoot)
subfolders = this.TryConsolidate(root, folder, subfolders.ToArray());
foreach (ModFolder subfolder in subfolders)
diff --git a/src/SMAPI.Toolkit/ModToolkit.cs b/src/SMAPI.Toolkit/ModToolkit.cs
index 51f6fa24..ce14b057 100644
--- a/src/SMAPI.Toolkit/ModToolkit.cs
+++ b/src/SMAPI.Toolkit/ModToolkit.cs
@@ -72,17 +72,19 @@ namespace StardewModdingAPI.Toolkit
/// <summary>Extract information about all mods in the given folder.</summary>
/// <param name="rootPath">The root folder containing mods.</param>
- public IEnumerable<ModFolder> GetModFolders(string rootPath)
+ /// <param name="useCaseInsensitiveFilePaths">Whether to match file paths case-insensitively, even on Linux.</param>
+ public IEnumerable<ModFolder> GetModFolders(string rootPath, bool useCaseInsensitiveFilePaths)
{
- return new ModScanner(this.JsonHelper).GetModFolders(rootPath);
+ return new ModScanner(this.JsonHelper).GetModFolders(rootPath, useCaseInsensitiveFilePaths);
}
/// <summary>Extract information about all mods in the given folder.</summary>
/// <param name="rootPath">The root folder containing mods. Only the <paramref name="modPath"/> will be searched, but this field allows it to be treated as a potential mod folder of its own.</param>
/// <param name="modPath">The mod path to search.</param>
- public IEnumerable<ModFolder> GetModFolders(string rootPath, string modPath)
+ /// <param name="useCaseInsensitiveFilePaths">Whether to match file paths case-insensitively, even on Linux.</param>
+ public IEnumerable<ModFolder> GetModFolders(string rootPath, string modPath, bool useCaseInsensitiveFilePaths)
{
- return new ModScanner(this.JsonHelper).GetModFolders(rootPath, modPath);
+ return new ModScanner(this.JsonHelper).GetModFolders(rootPath, modPath, useCaseInsensitiveFilePaths);
}
/// <summary>Get an update URL for an update key (if valid).</summary>
diff --git a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs
index 3c9308f2..1a003c51 100644
--- a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs
+++ b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs
@@ -108,12 +108,11 @@ namespace StardewModdingAPI.Toolkit.Serialization
/// <summary>Deserialize JSON text if possible.</summary>
/// <typeparam name="TModel">The model type.</typeparam>
/// <param name="json">The raw JSON text.</param>
- public TModel Deserialize<TModel>(string json)
+ public TModel? Deserialize<TModel>(string json)
{
try
{
- return JsonConvert.DeserializeObject<TModel>(json, this.JsonSettings)
- ?? throw new InvalidOperationException($"Couldn't deserialize model type '{typeof(TModel)}' from empty or null JSON.");
+ return JsonConvert.DeserializeObject<TModel>(json, this.JsonSettings);
}
catch (JsonReaderException)
{
diff --git a/src/SMAPI.Toolkit/Utilities/CaseInsensitivePathLookup.cs b/src/SMAPI.Toolkit/Utilities/PathLookups/CaseInsensitivePathLookup.cs
index 12fad008..9cc00737 100644
--- a/src/SMAPI.Toolkit/Utilities/CaseInsensitivePathLookup.cs
+++ b/src/SMAPI.Toolkit/Utilities/PathLookups/CaseInsensitivePathLookup.cs
@@ -2,10 +2,10 @@ using System;
using System.Collections.Generic;
using System.IO;
-namespace StardewModdingAPI.Toolkit.Utilities
+namespace StardewModdingAPI.Toolkit.Utilities.PathLookups
{
- /// <summary>Provides an API for case-insensitive relative path lookups within a root directory.</summary>
- internal class CaseInsensitivePathLookup
+ /// <summary>An API for case-insensitive relative path lookups within a root directory.</summary>
+ internal class CaseInsensitivePathLookup : IFilePathLookup
{
/*********
** Fields
@@ -32,24 +32,19 @@ namespace StardewModdingAPI.Toolkit.Utilities
this.RelativePathCache = new(() => this.GetRelativePathCache(searchOption));
}
- /// <summary>Get the exact capitalization for a given relative file path.</summary>
- /// <param name="relativePath">The relative path.</param>
- /// <remarks>Returns the resolved path in file path format, else the normalized <paramref name="relativePath"/>.</remarks>
+ /// <inheritdoc />
public string GetFilePath(string relativePath)
{
return this.GetImpl(PathUtilities.NormalizePath(relativePath));
}
- /// <summary>Get the exact capitalization for a given asset name.</summary>
- /// <param name="relativePath">The relative path.</param>
- /// <remarks>Returns the resolved path in asset name format, else the normalized <paramref name="relativePath"/>.</remarks>
+ /// <inheritdoc />
public string GetAssetName(string relativePath)
{
return this.GetImpl(PathUtilities.NormalizeAssetName(relativePath));
}
- /// <summary>Add a relative path that was just created by a SMAPI API.</summary>
- /// <param name="relativePath">The relative path. This must already be normalized in asset name or file path format.</param>
+ /// <inheritdoc />
public void Add(string relativePath)
{
// skip if cache isn't created yet (no need to add files manually in that case)
diff --git a/src/SMAPI.Toolkit/Utilities/PathLookups/IFilePathLookup.cs b/src/SMAPI.Toolkit/Utilities/PathLookups/IFilePathLookup.cs
new file mode 100644
index 00000000..678e1383
--- /dev/null
+++ b/src/SMAPI.Toolkit/Utilities/PathLookups/IFilePathLookup.cs
@@ -0,0 +1,20 @@
+namespace StardewModdingAPI.Toolkit.Utilities.PathLookups
+{
+ /// <summary>An API for relative path lookups within a root directory.</summary>
+ internal interface IFilePathLookup
+ {
+ /// <summary>Get the actual path for a given relative file path.</summary>
+ /// <param name="relativePath">The relative path.</param>
+ /// <remarks>Returns the resolved path in file path format, else the normalized <paramref name="relativePath"/>.</remarks>
+ string GetFilePath(string relativePath);
+
+ /// <summary>Get the actual path for a given asset name.</summary>
+ /// <param name="relativePath">The relative path.</param>
+ /// <remarks>Returns the resolved path in asset name format, else the normalized <paramref name="relativePath"/>.</remarks>
+ string GetAssetName(string relativePath);
+
+ /// <summary>Add a relative path that was just created by a SMAPI API.</summary>
+ /// <param name="relativePath">The relative path. This must already be normalized in asset name or file path format.</param>
+ void Add(string relativePath);
+ }
+}
diff --git a/src/SMAPI.Toolkit/Utilities/PathLookups/MinimalPathLookup.cs b/src/SMAPI.Toolkit/Utilities/PathLookups/MinimalPathLookup.cs
new file mode 100644
index 00000000..2cf14704
--- /dev/null
+++ b/src/SMAPI.Toolkit/Utilities/PathLookups/MinimalPathLookup.cs
@@ -0,0 +1,31 @@
+namespace StardewModdingAPI.Toolkit.Utilities.PathLookups
+{
+ /// <summary>An API for relative path lookups within a root directory with minimal preprocessing.</summary>
+ internal class MinimalPathLookup : IFilePathLookup
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>A singleton instance for reuse.</summary>
+ public static readonly MinimalPathLookup Instance = new();
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <inheritdoc />
+ public string GetFilePath(string relativePath)
+ {
+ return PathUtilities.NormalizePath(relativePath);
+ }
+
+ /// <inheritdoc />
+ public string GetAssetName(string relativePath)
+ {
+ return PathUtilities.NormalizeAssetName(relativePath);
+ }
+
+ /// <inheritdoc />
+ public void Add(string relativePath) { }
+ }
+}
diff --git a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json
index 75a3f8c7..53c9db82 100644
--- a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json
+++ b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json
@@ -168,6 +168,15 @@
},
/*********
+ ** Broke in SMAPI 3.14.0
+ *********/
+ "Dynamic Game Assets": {
+ "ID": "spacechase0.DynamicGameAssets",
+ "~1.4.1 | Status": "AssumeBroken",
+ "~1.4.1 | StatusReasonDetails": "causes runtime errors fixed in Dynamic Game Assets 1.4.2"
+ },
+
+ /*********
** Broke in SDV 1.5.5
*********/
"Animated Portrait Framework": {
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index f5da286a..b1a9cc82 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -50,7 +50,7 @@ namespace StardewModdingAPI
internal static int? LogScreenId { get; set; }
/// <summary>SMAPI's current raw semantic version.</summary>
- internal static string RawApiVersion = "3.14.0";
+ internal static string RawApiVersion = "3.14.1";
}
/// <summary>Contains SMAPI's constants and assumptions.</summary>
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 84fff250..4f52d57e 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -16,6 +16,7 @@ using StardewModdingAPI.Internal;
using StardewModdingAPI.Metadata;
using StardewModdingAPI.Toolkit.Serialization;
using StardewModdingAPI.Toolkit.Utilities;
+using StardewModdingAPI.Toolkit.Utilities.PathLookups;
using StardewValley;
using StardewValley.GameData;
using xTile;
@@ -31,8 +32,8 @@ namespace StardewModdingAPI.Framework
/// <summary>An asset key prefix for assets from SMAPI mod folders.</summary>
private readonly string ManagedPrefix = "SMAPI";
- /// <summary>Whether to enable more aggressive memory optimizations.</summary>
- private readonly bool AggressiveMemoryOptimizations;
+ /// <summary>Get a file path lookup for the given directory.</summary>
+ private readonly Func<string, IFilePathLookup> GetFilePathLookup;
/// <summary>Encapsulates monitoring and logging.</summary>
private readonly IMonitor Monitor;
@@ -80,6 +81,14 @@ namespace StardewModdingAPI.Framework
/// <summary>The cached asset load/edit operations to apply, indexed by asset name.</summary>
private readonly TickCacheDictionary<IAssetName, AssetOperationGroup[]> AssetOperationsByKey = new();
+ /// <summary>A cache of asset operation groups created for legacy <see cref="IAssetLoader"/> implementations.</summary>
+ [Obsolete]
+ private readonly Dictionary<IAssetLoader, Dictionary<Type, AssetOperationGroup>> LegacyLoaderCache = new(ReferenceEqualityComparer.Instance);
+
+ /// <summary>A cache of asset operation groups created for legacy <see cref="IAssetEditor"/> implementations.</summary>
+ [Obsolete]
+ private readonly Dictionary<IAssetEditor, Dictionary<Type, AssetOperationGroup>> LegacyEditorCache = new(ReferenceEqualityComparer.Instance);
+
/*********
** Accessors
@@ -114,12 +123,12 @@ namespace StardewModdingAPI.Framework
/// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
/// <param name="onLoadingFirstAsset">A callback to invoke the first time *any* game content manager loads an asset.</param>
/// <param name="onAssetLoaded">A callback to invoke when an asset is fully loaded.</param>
- /// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param>
+ /// <param name="getFilePathLookup">Get a file path lookup for the given directory.</param>
/// <param name="onAssetsInvalidated">A callback to invoke when any asset names have been invalidated from the cache.</param>
/// <param name="requestAssetOperations">Get the load/edit operations to apply to an asset by querying registered <see cref="IContentEvents.AssetRequested"/> event handlers.</param>
- public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, bool aggressiveMemoryOptimizations, Action<IList<IAssetName>> onAssetsInvalidated, Func<IAssetInfo, IList<AssetOperationGroup>> requestAssetOperations)
+ public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, Func<string, IFilePathLookup> getFilePathLookup, Action<IList<IAssetName>> onAssetsInvalidated, Func<IAssetInfo, IList<AssetOperationGroup>> requestAssetOperations)
{
- this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations;
+ this.GetFilePathLookup = getFilePathLookup;
this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor));
this.Reflection = reflection;
this.JsonHelper = jsonHelper;
@@ -139,26 +148,11 @@ namespace StardewModdingAPI.Framework
reflection: reflection,
onDisposing: this.OnDisposing,
onLoadingFirstAsset: onLoadingFirstAsset,
- onAssetLoaded: onAssetLoaded,
- aggressiveMemoryOptimizations: aggressiveMemoryOptimizations
+ onAssetLoaded: onAssetLoaded
)
);
- var contentManagerForAssetPropagation = new GameContentManagerForAssetPropagation(
- name: nameof(GameContentManagerForAssetPropagation),
- serviceProvider: serviceProvider,
- rootDirectory: rootDirectory,
- currentCulture: currentCulture,
- coordinator: this,
- monitor: monitor,
- reflection: reflection,
- onDisposing: this.OnDisposing,
- onLoadingFirstAsset: onLoadingFirstAsset,
- onAssetLoaded: onAssetLoaded,
- aggressiveMemoryOptimizations: aggressiveMemoryOptimizations
- );
- this.ContentManagers.Add(contentManagerForAssetPropagation);
this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory);
- this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, contentManagerForAssetPropagation, this.Monitor, reflection, aggressiveMemoryOptimizations, name => this.ParseAssetName(name, allowLocales: true));
+ this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, this.Monitor, reflection, name => this.ParseAssetName(name, allowLocales: true));
this.Locale