summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/Content/AssetName.cs108
-rw-r--r--src/SMAPI/Framework/Models/SConfig.cs34
-rw-r--r--src/SMAPI/Framework/SCore.cs19
3 files changed, 108 insertions, 53 deletions
diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs
index 148354a1..99968299 100644
--- a/src/SMAPI/Framework/Content/AssetName.cs
+++ b/src/SMAPI/Framework/Content/AssetName.cs
@@ -1,6 +1,8 @@
using System;
using StardewModdingAPI.Toolkit.Utilities;
+using StardewModdingAPI.Utilities.AssetPathUtilities;
using StardewValley;
+using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities;
namespace StardewModdingAPI.Framework.Content
{
@@ -94,10 +96,26 @@ namespace StardewModdingAPI.Framework.Content
if (string.IsNullOrWhiteSpace(assetName))
return false;
- assetName = PathUtilities.NormalizeAssetName(assetName);
+ AssetNamePartEnumerator curParts = new(useBaseName ? this.BaseName : this.Name);
+ AssetNamePartEnumerator otherParts = new(assetName.AsSpan().Trim());
- string compareTo = useBaseName ? this.BaseName : this.Name;
- return compareTo.Equals(assetName, StringComparison.OrdinalIgnoreCase);
+ while (true)
+ {
+ bool curHasMore = curParts.MoveNext();
+ bool otherHasMore = otherParts.MoveNext();
+
+ // mismatch: lengths differ
+ if (otherHasMore != curHasMore)
+ return false;
+
+ // match: both reached the end without a mismatch
+ if (!curHasMore)
+ return true;
+
+ // mismatch: current segment is different
+ if (!curParts.Current.Equals(otherParts.Current, StringComparison.OrdinalIgnoreCase))
+ return false;
+ }
}
/// <inheritdoc />
@@ -119,42 +137,70 @@ namespace StardewModdingAPI.Framework.Content
if (prefix is null)
return false;
- string rawTrimmed = prefix.Trim();
+ // get initial values
+ ReadOnlySpan<char> trimmedPrefix = prefix.AsSpan().Trim();
+ if (trimmedPrefix.Length == 0)
+ return true;
+ ReadOnlySpan<char> pathSeparators = new(ToolkitPathUtilities.PossiblePathSeparators); // just to simplify calling other span APIs
- // asset keys can't have a leading slash, but NormalizeAssetName will trim them
- if (rawTrimmed.StartsWith('/') || rawTrimmed.StartsWith('\\'))
+ // asset keys can't have a leading slash, but AssetPathYielder will trim them
+ if (pathSeparators.Contains(trimmedPrefix[0]))
return false;
- // normalize prefix
+ // compare segments
+ AssetNamePartEnumerator curParts = new(this.Name);
+ AssetNamePartEnumerator prefixParts = new(trimmedPrefix);
+ while (true)
{
- string normalized = PathUtilities.NormalizeAssetName(prefix);
+ bool curHasMore = curParts.MoveNext();
+ bool prefixHasMore = prefixParts.MoveNext();
- // keep trailing slash
- if (rawTrimmed.EndsWith('/') || rawTrimmed.EndsWith('\\'))
- normalized += PathUtilities.PreferredAssetSeparator;
+ // reached end for one side
+ if (prefixHasMore != curHasMore)
+ {
+ // mismatch: prefix is longer
+ if (prefixHasMore)
+ return false;
- prefix = normalized;
- }
+ // match if subfolder paths are fine (e.g. prefix 'Data/Events' with target 'Data/Events/Beach')
+ return allowSubfolder;
+ }
- // compare
- if (prefix.Length == 0)
- return true;
+ // previous segments matched exactly and both reached the end
+ // match if prefix doesn't end with '/' (which should only match subfolders)
+ if (!prefixHasMore)
+ return !pathSeparators.Contains(trimmedPrefix[^1]);
- return
- this.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
- && (
- allowPartialWord
- || this.Name.Length == prefix.Length
- || !char.IsLetterOrDigit(prefix[^1]) // last character in suffix is word separator
- || !char.IsLetterOrDigit(this.Name[prefix.Length]) // or first character after it is
- )
- && (
- allowSubfolder
- || this.Name.Length == prefix.Length
- || !this.Name[prefix.Length..].Contains(PathUtilities.PreferredAssetSeparator)
- );
- }
+ // compare segment
+ if (curParts.Current.Length == prefixParts.Current.Length)
+ {
+ // mismatch: segments aren't equivalent
+ if (!curParts.Current.Equals(prefixParts.Current, StringComparison.OrdinalIgnoreCase))
+ return false;
+ }
+ else
+ {
+ // mismatch: prefix has more beyond this, and this segment isn't an exact match
+ if (prefixParts.Remainder.Length != 0)
+ return false;
+
+ // mismatch: cur segment doesn't start with prefix
+ if (!curParts.Current.StartsWith(prefixParts.Current, StringComparison.OrdinalIgnoreCase))
+ return false;
+ // mismatch: something like "Maps/" would need an exact match
+ if (pathSeparators.Contains(trimmedPrefix[^1]))
+ return false;
+
+ // mismatch: partial word match not allowed, and the first or last letter of the suffix isn't a word separator
+ if (!allowPartialWord && char.IsLetterOrDigit(prefixParts.Current[^1]) && char.IsLetterOrDigit(curParts.Current[prefixParts.Current.Length]))
+ return false;
+
+ // possible match
+ return allowSubfolder || (pathSeparators.Contains(trimmedPrefix[^1]) ? curParts.Remainder.IndexOfAny(ToolkitPathUtilities.PossiblePathSeparators) < 0 : curParts.Remainder.Length == 0);
+ }
+ }
+ }
/// <inheritdoc />
public bool IsDirectlyUnderPath(string? assetFolder)
@@ -162,7 +208,7 @@ namespace StardewModdingAPI.Framework.Content
if (assetFolder is null)
return false;
- return this.StartsWith(assetFolder + "/", allowPartialWord: false, allowSubfolder: false);
+ return this.StartsWith(assetFolder + ToolkitPathUtilities.PreferredPathSeparator, allowPartialWord: false, allowSubfolder: false);
}
/// <inheritdoc />
diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs
index bceb0940..825ebb44 100644
--- a/src/SMAPI/Framework/Models/SConfig.cs
+++ b/src/SMAPI/Framework/Models/SConfig.cs
@@ -16,6 +16,7 @@ namespace StardewModdingAPI.Framework.Models
private static readonly IDictionary<string, object> DefaultValues = new Dictionary<string, object>
{
[nameof(CheckForUpdates)] = true,
+ [nameof(ListenForConsoleInput)] = true,
[nameof(ParanoidWarnings)] = Constants.IsDebugBuild,
[nameof(UseBetaChannel)] = Constants.ApiVersion.IsPrerelease(),
[nameof(GitHubProjectName)] = "Pathoschild/SMAPI",
@@ -48,6 +49,9 @@ namespace StardewModdingAPI.Framework.Models
/// <summary>Whether to check for newer versions of SMAPI and mods on startup.</summary>
public bool CheckForUpdates { get; set; }
+ /// <summary>Whether SMAPI should listen for console input to support console commands.</summary>
+ public bool ListenForConsoleInput { get; set; }
+
/// <summary>Whether to add a section to the 'mod issues' list for mods which which directly use potentially sensitive .NET APIs like file or shell access.</summary>
public bool ParanoidWarnings { get; set; }
@@ -87,23 +91,25 @@ namespace StardewModdingAPI.Framework.Models
** Public methods
********/
/// <summary>Construct an instance.</summary>
- /// <param name="developerMode">Whether to enable development features.</param>
- /// <param name="checkForUpdates">Whether to check for newer versions of SMAPI and mods on startup.</param>
- /// <param name="paranoidWarnings">Whether to add a section to the 'mod issues' list for mods which which directly use potentially sensitive .NET APIs like file or shell access.</param>
- /// <param name="useBetaChannel">Whether to show beta versions as valid updates.</param>
- /// <param name="gitHubProjectName">SMAPI's GitHub project name, used to perform update checks.</param>
- /// <param name="webApiBaseUrl">The base URL for SMAPI's web API, used to perform update checks.</param>
- /// <param name="verboseLogging">The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</param>
- /// <param name="rewriteMods">Whether SMAPI should rewrite mods for compatibility.</param>
- /// <param name="useCaseInsensitivePaths">>Whether to make SMAPI file APIs case-insensitive, even on Linux.</param>
- /// <param name="logNetworkTraffic">Whether SMAPI should log network traffic.</param>
- /// <param name="consoleColors">The colors to use for text written to the SMAPI console.</param>
- /// <param name="suppressHarmonyDebugMode">Whether to prevent mods from enabling Harmony's debug mode, which impacts performance and creates a file on your desktop. Debug mode should never be enabled by a released mod.</param>
- /// <param name="suppressUpdateChecks">The mod IDs SMAPI should ignore when performing update checks or validating update keys.</param>
- public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks)
+ /// <param name="developerMode"><inheritdoc cref="DeveloperMode" path="/summary" /></param>
+ /// <param name="checkForUpdates"><inheritdoc cref="CheckForUpdates" path="/summary" /></param>
+ /// <param name="listenForConsoleInput"><inheritdoc cref="ListenForConsoleInput" path="/summary" /></param>
+ /// <param name="paranoidWarnings"><inheritdoc cref="ParanoidWarnings" path="/summary" /></param>
+ /// <param name="useBetaChannel"><inheritdoc cref="UseBetaChannel" path="/summary" /></param>
+ /// <param name="gitHubProjectName"><inheritdoc cref="GitHubProjectName" path="/summary" /></param>
+ /// <param name="webApiBaseUrl"><inheritdoc cref="WebApiBaseUrl" path="/summary" /></param>
+ /// <param name="verboseLogging"><inheritdoc cref="VerboseLogging" path="/summary" /></param>
+ /// <param name="rewriteMods"><inheritdoc cref="RewriteMods" path="/summary" /></param>
+ /// <param name="useCaseInsensitivePaths"><inheritdoc cref="UseCaseInsensitivePaths" path="/summary" /></param>
+ /// <param name="logNetworkTraffic"><inheritdoc cref="LogNetworkTraffic" path="/summary" /></param>
+ /// <param name="consoleColors"><inheritdoc cref="ConsoleColors" path="/summary" /></param>
+ /// <param name="suppressHarmonyDebugMode"><inheritdoc cref="SuppressHarmonyDebugMode" path="/summary" /></param>
+ /// <param name="suppressUpdateChecks"><inheritdoc cref="SuppressUpdateChecks" path="/summary" /></param>
+ public SConfig(bool developerMode, bool? checkForUpdates, bool? listenForConsoleInput, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks)
{
this.DeveloperMode = developerMode;
this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)];
+ this.ListenForConsoleInput = listenForConsoleInput ?? (bool)SConfig.DefaultValues[nameof(this.ListenForConsoleInput)];
this.ParanoidWarnings = paranoidWarnings ?? (bool)SConfig.DefaultValues[nameof(this.ParanoidWarnings)];
this.UseBetaChannel = useBetaChannel ?? (bool)SConfig.DefaultValues[nameof(this.UseBetaChannel)];
this.GitHubProjectName = gitHubProjectName;
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 40979b09..7bb54653 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -447,14 +447,17 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log("SMAPI found problems in your game's content files which are likely to cause errors or crashes. Consider uninstalling XNB mods or reinstalling the game.", LogLevel.Error);
// start SMAPI console
- new Thread(
- () => this.LogManager.RunConsoleInputLoop(
- commandManager: this.CommandManager,
- reloadTranslations: this.ReloadTranslations,
- handleInput: input => this.RawCommandQueue.Add(input),
- continueWhile: () => this.IsGameRunning && !this.IsExiting
- )
- ).Start();
+ if (this.Settings.ListenForConsoleInput)
+ {
+ new Thread(
+ () => this.LogManager.RunConsoleInputLoop(
+ commandManager: this.CommandManager,
+ reloadTranslations: this.ReloadTranslations,
+ handleInput: input => this.RawCommandQueue.Add(input),
+ continueWhile: () => this.IsGameRunning && !this.IsExiting
+ )
+ ).Start();
+ }
}
/// <summary>Raised after an instance finishes loading its initial content.</summary>