diff --git a/src/SMAPI.Tests/Core/AssetNameTests.cs b/src/SMAPI.Tests/Core/AssetNameTests.cs index fdaa2c01..2d546ec7 100644 --- a/src/SMAPI.Tests/Core/AssetNameTests.cs +++ b/src/SMAPI.Tests/Core/AssetNameTests.cs @@ -265,6 +265,25 @@ namespace SMAPI.Tests.Core return name.StartsWith(otherAssetName, allowPartialWord: true, allowSubfolder: allowSubfolder); } + // The enumerator strips the trailing path separator, so each of these cases has to be handled on each branch. + [TestCase("Mods/SomeMod", "Mods/", false, ExpectedResult = true)] + [TestCase("Mods/SomeMod", "Mods", false, ExpectedResult = false)] + [TestCase("Mods/Jasper/Data", "Mods/Jas/", false, ExpectedResult = false)] + [TestCase("Mods/Jasper/Data", "Mods/Jas", false, ExpectedResult = false)] + [TestCase("Mods/Jas", "Mods/Jas/", false, ExpectedResult = false)] + [TestCase("Mods/Jas", "Mods/Jas", false, ExpectedResult = true)] + public bool StartsWith_PrefixHasSeparator(string mainAssetName, string otherAssetName, bool allowSubfolder) + { + // arrange + mainAssetName = PathUtilities.NormalizeAssetName(mainAssetName); + + // act + AssetName name = AssetName.Parse(mainAssetName, _ => null); + + // assert value + return name.StartsWith(otherAssetName, allowPartialWord: true, allowSubfolder: allowSubfolder); + } + /**** ** GetHashCode diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index 99968299..8355f9ec 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -162,8 +162,14 @@ namespace StardewModdingAPI.Framework.Content if (prefixHasMore) return false; - // match if subfolder paths are fine (e.g. prefix 'Data/Events' with target 'Data/Events/Beach') - return allowSubfolder; + // match: every segment in the prefix matched and subfolders are allowed (e.g. prefix 'Data/Events' with target 'Data/Events/Beach') + if (allowSubfolder) + return true; + + // Special case: the prefix ends with a path separator, but subfolders aren't allowed. This case + // matches if there's no further path separator in the asset name *after* the current separator. + // For example, the prefix 'A/B/' matches 'A/B/C' but not 'A/B/C/D'. + return pathSeparators.Contains(trimmedPrefix[^1]) && curParts.Remainder.Length == 0; } // previous segments matched exactly and both reached the end