diff options
Diffstat (limited to 'src')
55 files changed, 188 insertions, 154 deletions
diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index 1457848b..dc96e2e8 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -468,7 +468,7 @@ namespace StardewModdingApi.Installer } // find target folder - ModFolder targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(sourceMod.Manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase) == true); + ModFolder targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(sourceMod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase) == true); DirectoryInfo defaultTargetFolder = new DirectoryInfo(Path.Combine(paths.ModsPath, sourceMod.Directory.Name)); DirectoryInfo targetFolder = targetMod?.Directory ?? defaultTargetFolder; this.PrintDebug(targetFolder.FullName == defaultTargetFolder.FullName @@ -808,7 +808,7 @@ namespace StardewModdingApi.Installer continue; // should never happen // delete packaged mods (newer version bundled into SMAPI) - if (isDir && packagedModNames.Contains(entry.Name, StringComparer.InvariantCultureIgnoreCase)) + if (isDir && packagedModNames.Contains(entry.Name, StringComparer.OrdinalIgnoreCase)) { this.PrintDebug($" Deleting {entry.Name} because it's bundled into SMAPI..."); this.InteractivelyDelete(entry.FullName); diff --git a/src/SMAPI.Installer/Program.cs b/src/SMAPI.Installer/Program.cs index b7fa45f5..dc6c97f4 100644 --- a/src/SMAPI.Installer/Program.cs +++ b/src/SMAPI.Installer/Program.cs @@ -67,7 +67,7 @@ namespace StardewModdingApi.Installer AssemblyName name = new AssemblyName(e.Name); foreach (FileInfo dll in new DirectoryInfo(Program.InternalFilesPath).EnumerateFiles("*.dll")) { - if (name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.InvariantCultureIgnoreCase)) + if (name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.OrdinalIgnoreCase)) return Assembly.LoadFrom(dll.FullName); } return null; diff --git a/src/SMAPI.Installer/assets/unix-launcher.sh b/src/SMAPI.Installer/assets/unix-launcher.sh index b72eed22..1d97d487 100644 --- a/src/SMAPI.Installer/assets/unix-launcher.sh +++ b/src/SMAPI.Installer/assets/unix-launcher.sh @@ -46,80 +46,64 @@ else if [ "$ARCH" == "x86_64" ]; then ln -sf mcs.bin.x86_64 mcs cp StardewValley.bin.x86_64 StardewModdingAPI.bin.x86_64 - LAUNCHER="./StardewModdingAPI.bin.x86_64 $*" + LAUNCHER="./StardewModdingAPI.bin.x86_64" else ln -sf mcs.bin.x86 mcs cp StardewValley.bin.x86 StardewModdingAPI.bin.x86 - LAUNCHER="./StardewModdingAPI.bin.x86 $*" + LAUNCHER="./StardewModdingAPI.bin.x86" fi + export LAUNCHER # get cross-distro version of POSIX command COMMAND="" if command -v command 2>/dev/null; then COMMAND="command -v" elif type type 2>/dev/null; then - COMMAND="type" + COMMAND="type -p" fi # select terminal (prefer xterm for best compatibility, then known supported terminals) for terminal in xterm gnome-terminal kitty terminator xfce4-terminal konsole terminal termite alacritty mate-terminal x-terminal-emulator; do if $COMMAND "$terminal" 2>/dev/null; then - # Find the true shell behind x-terminal-emulator - if [ "$(basename "$(readlink -f $(which "$terminal"))")" != "x-terminal-emulator" ]; then - export LAUNCHTERM=$terminal - break; - else - export LAUNCHTERM="$(basename "$(readlink -f $(which x-terminal-emulator))")" - # Remember that we're using x-terminal-emulator just in case it points outside the $PATH - export XTE=1 - break; - fi + export LAUNCHTERM=$terminal + break; fi done - # if no terminal was found, run in current shell or with no output - if [ -z "$LAUNCHTERM" ]; then - sh -c 'TERM=xterm $LAUNCHER' - if [ $? -eq 127 ]; then - $LAUNCHER --no-terminal - fi - exit + # find the true shell behind x-terminal-emulator + if [ "$LAUNCHTERM" = "x-terminal-emulator" ]; then + export LAUNCHTERM="$(basename "$(readlink -f $(COMMAND x-terminal-emulator))")" fi # run in selected terminal and account for quirks case $LAUNCHTERM in - terminator) - # Terminator converts -e to -x when used through x-terminal-emulator for some reason - if $XTE; then - terminator -e "sh -c 'TERM=xterm $LAUNCHER'" - else - terminator -x "sh -c 'TERM=xterm $LAUNCHER'" - fi + terminal|termite) + # LAUNCHTERM consumes only one argument after -e + # options containing space characters are unsupported + exec $LAUNCHTERM -e "env TERM=xterm $LAUNCHER $@" ;; - kitty) - # Kitty overrides the TERM varible unless you set it explicitly - kitty -o term=xterm $LAUNCHER + xterm|konsole|alacritty) + # LAUNCHTERM consumes all arguments after -e + exec $LAUNCHTERM -e env TERM=xterm $LAUNCHER "$@" ;; - alacritty) - # Alacritty doesn't like the double quotes or the variable - if [ "$ARCH" == "x86_64" ]; then - alacritty -e sh -c 'TERM=xterm ./StardewModdingAPI.bin.x86_64 $*' - else - alacritty -e sh -c 'TERM=xterm ./StardewModdingAPI.bin.x86 $*' - fi + terminator|xfce4-terminal|mate-terminal) + # LAUNCHTERM consumes all arguments after -x + exec $LAUNCHTERM -x env TERM=xterm $LAUNCHER "$@" ;; - xterm|xfce4-terminal|gnome-terminal|terminal|termite|mate-terminal) - $LAUNCHTERM -e "sh -c 'TERM=xterm $LAUNCHER'" + gnome-terminal) + # LAUNCHTERM consumes all arguments after -- + exec $LAUNCHTERM -- env TERM=xterm $LAUNCHER "$@" ;; - konsole) - konsole -p Environment=TERM=xterm -e "$LAUNCHER" + kitty) + # LAUNCHTERM consumes all trailing arguments + exec $LAUNCHTERM env TERM=xterm $LAUNCHER "$@" ;; *) # If we don't know the terminal, just try to run it in the current shell. - sh -c 'TERM=xterm $LAUNCHER' + env TERM=xterm $LAUNCHER "$@" # if THAT fails, launch with no output if [ $? -eq 127 ]; then - $LAUNCHER --no-terminal + exec $LAUNCHER --no-terminal "$@" fi esac fi diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj index 5ae6574d..9c230203 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj @@ -9,7 +9,7 @@ <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.10.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" /> <PackageReference Include="NUnit" Version="3.12.0" /> - <PackageReference Include="NUnit3TestAdapter" Version="3.16.1"> + <PackageReference Include="NUnit3TestAdapter" Version="3.17.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> diff --git a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs index f0363a3e..636c3669 100644 --- a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs +++ b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs @@ -33,7 +33,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework /// <exception cref="UserErrorException">The mod package isn't valid.</exception> public ModFileManager(string projectDir, string targetDir, Regex[] ignoreFilePatterns, bool validateRequiredModFiles) { - this.Files = new Dictionary<string, FileInfo>(StringComparer.InvariantCultureIgnoreCase); + this.Files = new Dictionary<string, FileInfo>(StringComparer.OrdinalIgnoreCase); // validate paths if (!Directory.Exists(projectDir)) @@ -68,7 +68,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework /// <summary>Get the files in the mod package.</summary> public IDictionary<string, FileInfo> GetFiles() { - return new Dictionary<string, FileInfo>(this.Files, StringComparer.InvariantCultureIgnoreCase); + return new Dictionary<string, FileInfo>(this.Files, StringComparer.OrdinalIgnoreCase); } /// <summary>Get a semantic version from the mod manifest.</summary> @@ -165,8 +165,8 @@ namespace StardewModdingAPI.ModBuildConfig.Framework || this.EqualsInvariant(file.Name, "Newtonsoft.Json.xml") // code analysis files - || file.Name.EndsWith(".CodeAnalysisLog.xml", StringComparison.InvariantCultureIgnoreCase) - || file.Name.EndsWith(".lastcodeanalysissucceeded", StringComparison.InvariantCultureIgnoreCase) + || file.Name.EndsWith(".CodeAnalysisLog.xml", StringComparison.OrdinalIgnoreCase) + || file.Name.EndsWith(".lastcodeanalysissucceeded", StringComparison.OrdinalIgnoreCase) // OS metadata files || this.EqualsInvariant(file.Name, ".DS_Store") @@ -183,7 +183,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework { if (str == null) return other == null; - return str.Equals(other, StringComparison.InvariantCultureIgnoreCase); + return str.Equals(other, StringComparison.OrdinalIgnoreCase); } } } diff --git a/src/SMAPI.ModBuildConfig/build/smapi.targets b/src/SMAPI.ModBuildConfig/build/smapi.targets index bfee3b33..03db7490 100644 --- a/src/SMAPI.ModBuildConfig/build/smapi.targets +++ b/src/SMAPI.ModBuildConfig/build/smapi.targets @@ -20,12 +20,12 @@ <ModZipPath Condition="'$(ModZipPath)' == ''">$(TargetDir)</ModZipPath> <EnableModDeploy Condition="'$(EnableModDeploy)' == ''">true</EnableModDeploy> <EnableModZip Condition="'$(EnableModZip)' == ''">true</EnableModZip> - <EnableHarmony Condition="'$(EnableModZip)' == ''">false</EnableHarmony> - <EnableGameDebugging Condition="$(EnableGameDebugging) == ''">true</EnableGameDebugging> + <EnableHarmony Condition="'$(EnableHarmony)' == ''">false</EnableHarmony> + <EnableGameDebugging Condition="'$(EnableGameDebugging)' == ''">true</EnableGameDebugging> <CopyModReferencesToBuildOutput Condition="'$(CopyModReferencesToBuildOutput)' == '' OR ('$(CopyModReferencesToBuildOutput)' != 'true' AND '$(CopyModReferencesToBuildOutput)' != 'false')">false</CopyModReferencesToBuildOutput> </PropertyGroup> - <PropertyGroup Condition="$(OS) == 'Windows_NT' AND $(EnableGameDebugging) == 'true'"> + <PropertyGroup Condition="'$(OS)' == 'Windows_NT' AND '$(EnableGameDebugging)' == 'true'"> <!-- enable game debugging --> <StartAction>Program</StartAction> <StartProgram>$(GamePath)\StardewModdingAPI.exe</StartProgram> @@ -47,7 +47,7 @@ </ItemGroup> <!-- Windows --> - <ItemGroup Condition="$(OS) == 'Windows_NT'"> + <ItemGroup Condition="'$(OS)' == 'Windows_NT'"> <Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" /> <Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" /> <Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="$(CopyModReferencesToBuildOutput)" /> @@ -56,7 +56,7 @@ </ItemGroup> <!-- Linux/Mac --> - <ItemGroup Condition="$(OS) != 'Windows_NT'"> + <ItemGroup Condition="'$(OS)' != 'Windows_NT'"> <Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="$(CopyModReferencesToBuildOutput)" /> </ItemGroup> diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs index 9c7082c9..e84445d7 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs @@ -64,7 +64,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands this.LogError($"Argument {index} ({name}) is required."); return false; } - if (oneOf?.Any() == true && !oneOf.Contains(this.Args[index], StringComparer.InvariantCultureIgnoreCase)) + if (oneOf?.Any() == true && !oneOf.Contains(this.Args[index], StringComparer.OrdinalIgnoreCase)) { this.LogError($"Argument {index} ({name}) must be one of {string.Join(", ", oneOf)}."); return false; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs index e9545575..1190a4ab 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs @@ -56,7 +56,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World return; // get target location - GameLocation location = Game1.locations.FirstOrDefault(p => p.Name != null && p.Name.Equals(locationName, StringComparison.InvariantCultureIgnoreCase)); + GameLocation location = Game1.locations.FirstOrDefault(p => p.Name != null && p.Name.Equals(locationName, StringComparison.OrdinalIgnoreCase)); if (location == null && locationName == "current") location = Game1.currentLocation; if (location == null) diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs index b618a308..d9e63126 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs @@ -44,8 +44,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData public bool NameContains(string substring) { return - this.Name.IndexOf(substring, StringComparison.InvariantCultureIgnoreCase) != -1 - || this.DisplayName.IndexOf(substring, StringComparison.InvariantCultureIgnoreCase) != -1; + this.Name.IndexOf(substring, StringComparison.OrdinalIgnoreCase) != -1 + || this.DisplayName.IndexOf(substring, StringComparison.OrdinalIgnoreCase) != -1; } /// <summary>Get whether the item name is exactly equal to a case-insensitive string.</summary> @@ -53,8 +53,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData public bool NameEquivalentTo(string name) { return - this.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase) - || this.DisplayName.Equals(name, StringComparison.InvariantCultureIgnoreCase); + this.Name.Equals(name, StringComparison.OrdinalIgnoreCase) + || this.DisplayName.Equals(name, StringComparison.OrdinalIgnoreCase); } } } diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index 6a17213c..37f5f8d1 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -77,7 +77,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework // furniture foreach (int id in this.TryLoad<int, string>("Data\\Furniture").Keys) { - if (id == 1466 || id == 1468) + if (id == 1466 || id == 1468 || id == 1680) yield return this.TryCreate(ItemType.Furniture, id, () => new TV(id, Vector2.Zero)); else yield return this.TryCreate(ItemType.Furniture, id, () => new Furniture(id, Vector2.Zero)); @@ -192,7 +192,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework SObject input = this.TryCreate(ItemType.Object, -1, () => new SObject(pair.Key, 1))?.Item as SObject; if (input == null || input.Category != SObject.FishCategory) continue; - Color color = TailoringMenu.GetDyeColor(input) ?? Color.Orange; + Color color = this.GetRoeColor(input); // yield roe SObject roe = null; @@ -259,12 +259,24 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework { try { - return new SearchableItem(type, id, createItem()); + var item = createItem(); + item.getDescription(); // force-load item data, so it crashes here if it's invalid + return new SearchableItem(type, id, item); } catch { return null; // if some item data is invalid, just don't include it } } + + /// <summary>Get the color to use a given fish's roe.</summary> + /// <param name="fish">The fish whose roe to color.</param> + /// <remarks>Derived from <see cref="StardewValley.Buildings.FishPond.GetFishProduce"/>.</remarks> + private Color GetRoeColor(SObject fish) + { + return fish.ParentSheetIndex == 698 // sturgeon + ? new Color(61, 55, 42) + : (TailoringMenu.GetDyeColor(fish) ?? Color.Orange); + } } } diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 7300cdf8..1be55776 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.6.1", + "Version": "3.6.2", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.6.1" + "MinimumApiVersion": "3.6.2" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index cffd780d..c57ac162 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.6.1", + "Version": "3.6.2", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.6.1" + "MinimumApiVersion": "3.6.2" } diff --git a/src/SMAPI.Tests/SMAPI.Tests.csproj b/src/SMAPI.Tests/SMAPI.Tests.csproj index b1548e3a..6896808d 100644 --- a/src/SMAPI.Tests/SMAPI.Tests.csproj +++ b/src/SMAPI.Tests/SMAPI.Tests.csproj @@ -16,7 +16,7 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Moq" Version="4.14.1" /> + <PackageReference Include="Moq" Version="4.14.5" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="NUnit" Version="3.12.0" /> </ItemGroup> diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs index 34e2e1b8..89a22eaf 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs @@ -233,7 +233,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki // parse // Specified on the wiki in the form "remote version → mapped version; another remote version → mapped version" - IDictionary<string, string> map = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase); + IDictionary<string, string> map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (string pair in raw.Split(';')) { string[] versions = pair.Split('→'); diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs index 4eec3424..450d600a 100644 --- a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs +++ b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs @@ -30,7 +30,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning .GetCustomInstallPaths(platform) .Concat(this.GetDefaultInstallPaths(platform)) .Select(PathUtilities.NormalizePathSeparators) - .Distinct(StringComparer.InvariantCultureIgnoreCase); + .Distinct(StringComparer.OrdinalIgnoreCase); // yield valid folders foreach (string path in paths) diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs index 8b40c301..2167d3e5 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs @@ -68,7 +68,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData foreach (string part in parts.Take(parts.Length - 1)) { // 'default' - if (part.Equals("Default", StringComparison.InvariantCultureIgnoreCase)) + if (part.Equals("Default", StringComparison.OrdinalIgnoreCase)) { isDefault = true; continue; diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs index c892d820..3201c421 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs @@ -46,13 +46,13 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData public bool HasID(string id) { // try main ID - if (this.ID.Equals(id, StringComparison.InvariantCultureIgnoreCase)) + if (this.ID.Equals(id, StringComparison.OrdinalIgnoreCase)) return true; // try former IDs foreach (string formerID in this.FormerIDs) { - if (formerID.Equals(id, StringComparison.InvariantCultureIgnoreCase)) + if (formerID.Equals(id, StringComparison.OrdinalIgnoreCase)) return true; } diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs index f4857c7d..6d6b6417 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -18,22 +18,48 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning private readonly JsonHelper JsonHelper; /// <summary>A list of filesystem entry names to ignore when checking whether a folder should be treated as a mod.</summary> - private readonly HashSet<Regex> IgnoreFilesystemEntries = new HashSet<Regex> + private readonly HashSet<Regex> IgnoreFilesystemNames = new HashSet<Regex> { - // OS metadata files new Regex(@"^__folder_managed_by_vortex$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // Vortex mod manager new Regex(@"(?:^\._|^\.DS_Store$|^__MACOSX$|^mcs$)", RegexOptions.Compiled | RegexOptions.IgnoreCase), // MacOS - new Regex(@"^(?:desktop\.ini|Thumbs\.db)$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // Windows - new Regex(@"\.(?:url|lnk)$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // Windows shortcut files + new Regex(@"^(?:desktop\.ini|Thumbs\.db)$", RegexOptions.Compiled | RegexOptions.IgnoreCase) // Windows + }; - // other - new Regex(@"\.(?:bmp|gif|jpeg|jpg|png|psd|tif)$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // image files - new Regex(@"\.(?:md|rtf|txt)$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // text files - new Regex(@"\.(?:backup|bak|old)$", RegexOptions.Compiled | RegexOptions.IgnoreCase) // backup file + /// <summary>A list of file extensions to ignore when searching for mod files.</summary> + private readonly HashSet<string> IgnoreFileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + { + // text + ".doc", + ".docx", + ".md", + ".rtf", + ".txt", + + // images + ".bmp", + ".gif", + ".jpeg", + ".jpg", + ".png", + ".psd", + ".tif", + + // archives + ".rar", + ".zip", + + // backup files + ".backup", + ".bak", + ".old", + + // Windows shortcut files + ".url", + ".lnk" }; /// <summary>The extensions for files which an XNB mod may contain. If a mod doesn't have a <c>manifest.json</c> and contains *only* these file extensions, it should be considered an XNB mod.</summary> - private readonly HashSet<string> PotentialXnbModExtensions = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) + private readonly HashSet<string> PotentialXnbModExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { // XNB files ".xgs", @@ -258,7 +284,12 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning /// <param name="entry">The file or folder.</param> private bool IsRelevant(FileSystemInfo entry) { - return !this.IgnoreFilesystemEntries.Any(p => p.IsMatch(entry.Name)); + // ignored file extension + if (entry is FileInfo file && this.IgnoreFileExtensions.Contains(file.Extension)) + return false; + + // ignored entry name + return !this.IgnoreFilesystemNames.Any(p => p.IsMatch(entry.Name)); } /// <summary>Get whether a file is potentially part of an XNB mod.</summary> diff --git a/src/SMAPI.Toolkit/ModToolkit.cs b/src/SMAPI.Toolkit/ModToolkit.cs index 08fe0fed..695a2c52 100644 --- a/src/SMAPI.Toolkit/ModToolkit.cs +++ b/src/SMAPI.Toolkit/ModToolkit.cs @@ -22,7 +22,7 @@ namespace StardewModdingAPI.Toolkit private readonly string UserAgent; /// <summary>Maps vendor keys (like <c>Nexus</c>) to their mod URL template (where <c>{0}</c> is the mod ID). This doesn't affect update checks, which defer to the remote web API.</summary> - private readonly IDictionary<string, string> VendorModUrls = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) + private readonly IDictionary<string, string> VendorModUrls = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["Chucklefish"] = "https://community.playstarbound.com/resources/{0}", ["GitHub"] = "https://github.com/{0}/releases", diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 71ea0f12..9e9d824a 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -8,7 +8,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="HtmlAgilityPack" Version="1.11.23" /> + <PackageReference Include="HtmlAgilityPack" Version="1.11.24" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Pathoschild.Http.FluentClient" Version="4.0.0" /> <PackageReference Include="System.Management" Version="4.5.0" Condition="'$(OS)' == 'Windows_NT'" /> diff --git a/src/SMAPI.Toolkit/SemanticVersion.cs b/src/SMAPI.Toolkit/SemanticVersion.cs index 86db2820..1a76bec3 100644 --- a/src/SMAPI.Toolkit/SemanticVersion.cs +++ b/src/SMAPI.Toolkit/SemanticVersion.cs @@ -278,9 +278,9 @@ namespace StardewModdingAPI.Toolkit if (curParts[i] != otherParts[i]) { // unofficial is always lower-precedence - if (otherParts[i].Equals("unofficial", StringComparison.InvariantCultureIgnoreCase)) + if (otherParts[i].Equals("unofficial", StringComparison.OrdinalIgnoreCase)) return curNewer; - if (curParts[i].Equals("unofficial", StringComparison.InvariantCultureIgnoreCase)) + if (curParts[i].Equals("unofficial", StringComparison.OrdinalIgnoreCase)) return curOlder; // compare numerically if possible @@ -295,7 +295,7 @@ namespace StardewModdingAPI.Toolkit } // fallback (this should never happen) - return string.Compare(this.ToString(), new SemanticVersion(otherMajor, otherMinor, otherPatch, otherPlatformRelease, otherTag).ToString(), StringComparison.InvariantCultureIgnoreCase); + return string.Compare(this.ToString(), new SemanticVersion(otherMajor, otherMinor, otherPatch, otherPlatformRelease, otherTag).ToString(), StringComparison.OrdinalIgnoreCase); } /// <summary>Assert that the current version is valid.</summary> diff --git a/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs b/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs index 9aba53bf..10f88dde 100644 --- a/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs +++ b/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs @@ -12,7 +12,7 @@ namespace StardewModdingAPI.Toolkit.Serialization /// <param name="fieldName">The field name.</param> public static T ValueIgnoreCase<T>(this JObject obj, string fieldName) { - JToken token = obj.GetValue(fieldName, StringComparison.InvariantCultureIgnoreCase); + JToken token = obj.GetValue(fieldName, StringComparison.OrdinalIgnoreCase); return token != null ? token.Value<T>() : default(T); diff --git a/src/SMAPI.Web/Controllers/JsonValidatorController.cs b/src/SMAPI.Web/Controllers/JsonValidatorController.cs index 5f83eafd..6ba97749 100644 --- a/src/SMAPI.Web/Controllers/JsonValidatorController.cs +++ b/src/SMAPI.Web/Controllers/JsonValidatorController.cs @@ -280,7 +280,7 @@ namespace StardewModdingAPI.Web.Controllers IDictionary<string, string> errors = this.GetExtensionField<Dictionary<string, string>>(error.Schema, "@errorMessages"); if (errors == null) return null; - errors = new Dictionary<string, string>(errors, StringComparer.InvariantCultureIgnoreCase); + errors = new Dictionary<string, string>(errors, StringComparer.OrdinalIgnoreCase); // match error by type and message foreach ((string target, string errorMessage) in errors) @@ -289,7 +289,7 @@ namespace StardewModdingAPI.Web.Controllers continue; string[] parts = target.Split(':', 2); - if (parts[0].Equals(error.ErrorType.ToString(), StringComparison.InvariantCultureIgnoreCase) && Regex.IsMatch(error.Message, parts[1])) + if (parts[0].Equals(error.ErrorType.ToString(), StringComparison.OrdinalIgnoreCase) && Regex.IsMatch(error.Message, parts[1])) return errorMessage?.Trim(); } @@ -313,7 +313,7 @@ namespace StardewModdingAPI.Web.Controllers { foreach ((string curKey, JToken value) in schema.ExtensionData) { - if (curKey.Equals(key, StringComparison.InvariantCultureIgnoreCase)) + if (curKey.Equals(key, StringComparison.OrdinalIgnoreCase)) return value.ToObject<T>(); } } diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index db669bf9..cd5b6779 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -118,9 +118,9 @@ namespace StardewModdingAPI.Web.Controllers { // cross-reference data ModDataRecord record = this.ModDatabase.Get(search.ID); - WikiModEntry wikiEntry = wikiData.FirstOrDefault(entry => entry.ID.Contains(search.ID.Trim(), StringComparer.InvariantCultureIgnoreCase)); + WikiModEntry wikiEntry = wikiData.FirstOrDefault(entry => entry.ID.Contains(search.ID.Trim(), StringComparer.OrdinalIgnoreCase)); UpdateKey[] updateKeys = this.GetUpdateKeys(search.UpdateKeys, record, wikiEntry).ToArray(); - ModOverrideConfig overrides = this.Config.Value.ModOverrides.FirstOrDefault(p => p.ID.Equals(search.ID?.Trim(), StringComparison.InvariantCultureIgnoreCase)); + ModOverrideConfig overrides = this.Config.Value.ModOverrides.FirstOrDefault(p => p.ID.Equals(search.ID?.Trim(), StringComparison.OrdinalIgnoreCase)); bool allowNonStandardVersions = overrides?.AllowNonStandardVersions ?? false; // get latest versions diff --git a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs index 6b0ec1ec..9769793c 100644 --- a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs @@ -13,7 +13,7 @@ namespace StardewModdingAPI.Web.Framework.Caching.Mods ** Fields *********/ /// <summary>The cached mod data indexed by <c>{site key}:{ID}</c>.</summary> - private readonly IDictionary<string, Cached<IModPage>> Mods = new Dictionary<string, Cached<IModPage>>(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary<string, Cached<IModPage>> Mods = new Dictionary<string, Cached<IModPage>>(StringComparer.OrdinalIgnoreCase); /********* diff --git a/src/SMAPI.Web/Framework/Clients/GitHub/GitHubClient.cs b/src/SMAPI.Web/Framework/Clients/GitHub/GitHubClient.cs index 2f1eb854..671f077c 100644 --- a/src/SMAPI.Web/Framework/Clients/GitHub/GitHubClient.cs +++ b/src/SMAPI.Web/Framework/Clients/GitHub/GitHubClient.cs @@ -150,7 +150,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub /// <exception cref="ArgumentException">The repository key is invalid.</exception> private void AssertKeyFormat(string repo) { - if (repo == null || !repo.Contains("/") || repo.IndexOf("/", StringComparison.InvariantCultureIgnoreCase) != repo.LastIndexOf("/", StringComparison.InvariantCultureIgnoreCase)) + if (repo == null || !repo.Contains("/") || repo.IndexOf("/", StringComparison.OrdinalIgnoreCase) != repo.LastIndexOf("/", StringComparison.OrdinalIgnoreCase)) throw new ArgumentException($"The value '{repo}' isn't a valid GitHub repository key, must be a username and project name like 'Pathoschild/SMAPI'.", nameof(repo)); } } diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj index c6c0f774..23e0340d 100644 --- a/src/SMAPI.Web/SMAPI.Web.csproj +++ b/src/SMAPI.Web/SMAPI.Web.csproj @@ -12,14 +12,14 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Azure.Storage.Blobs" Version="12.4.2" /> - <PackageReference Include="Hangfire.AspNetCore" Version="1.7.11" /> + <PackageReference Include="Azure.Storage.Blobs" Version="12.4.4" /> + <PackageReference Include="Hangfire.AspNetCore" Version="1.7.12" /> <PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" /> - <PackageReference Include="HtmlAgilityPack" Version="1.11.23" /> - <PackageReference Include="Humanizer.Core" Version="2.8.11" /> + <PackageReference Include="HtmlAgilityPack" Version="1.11.24" /> + <PackageReference Include="Humanizer.Core" Version="2.8.26" /> <PackageReference Include="JetBrains.Annotations" Version="2020.1.0" /> <PackageReference Include="Markdig" Version="0.20.0" /> - <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.2" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.6" /> <PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.13" /> <PackageReference Include="Pathoschild.FluentNexus" Version="1.0.1" /> <PackageReference Include="Pathoschild.Http.FluentClient" Version="4.0.0" /> diff --git a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json index 726b50be..6e8a4e52 100644 --- a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json +++ b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json @@ -11,9 +11,9 @@ "title": "Format version", "description": "The format version. You should always use the latest version to enable the latest features and avoid obsolete behavior.", "type": "string", - "const": "1.14.0", + "const": "1.15.0", "@errorMessages": { - "const": "Incorrect value '@value'. This should be set to the latest format version, currently '1.14.0'." + "const": "Incorrect value '@value'. This should be set to the latest format version, currently '1.15.0'." } }, "ConfigSchema": { diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index a7e44de6..c1c99150 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -20,7 +20,7 @@ namespace StardewModdingAPI ** Public ****/ /// <summary>SMAPI's current semantic version.</summary> - public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.6.1"); + public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.6.2"); /// <summary>The minimum supported version of Stardew Valley.</summary> public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.4.1"); diff --git a/src/SMAPI/Framework/CommandManager.cs b/src/SMAPI/Framework/CommandManager.cs index eaa91c86..2b91d394 100644 --- a/src/SMAPI/Framework/CommandManager.cs +++ b/src/SMAPI/Framework/CommandManager.cs @@ -13,7 +13,7 @@ namespace StardewModdingAPI.Framework ** Fields *********/ /// <summary>The commands registered with SMAPI.</summary> - private readonly IDictionary<string, Command> Commands = new Dictionary<string, Command>(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary<string, Command> Commands = new Dictionary<string, Command>(StringComparer.OrdinalIgnoreCase); /********* diff --git a/src/SMAPI/Framework/Content/AssetDataForMap.cs b/src/SMAPI/Framework/Content/AssetDataForMap.cs index f66013ba..dee5b034 100644 --- a/src/SMAPI/Framework/Content/AssetDataForMap.cs +++ b/src/SMAPI/Framework/Content/AssetDataForMap.cs @@ -67,7 +67,7 @@ namespace StardewModdingAPI.Framework.Content { // change ID if needed so new tilesheets are added after vanilla ones (to avoid errors in hardcoded game logic) string id = sourceSheet.Id; - if (!id.StartsWith("z_", StringComparison.InvariantCultureIgnoreCase)) + if (!id.StartsWith("z_", StringComparison.OrdinalIgnoreCase)) id = $"z_{id}"; // change ID if it conflicts with an existing tilesheet diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs index 9b685e72..ed009499 100644 --- a/src/SMAPI/Framework/Content/AssetInfo.cs +++ b/src/SMAPI/Framework/Content/AssetInfo.cs @@ -47,7 +47,7 @@ namespace StardewModdingAPI.Framework.Content public bool AssetNameEquals(string path) { path = this.GetNormalizedPath(path); - return this.AssetName.Equals(path, StringComparison.InvariantCultureIgnoreCase); + return this.AssetName.Equals(path, StringComparison.OrdinalIgnoreCase); } diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index b0933ac6..2052f6bf 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -89,7 +89,7 @@ namespace StardewModdingAPI.Framework.Content public string NormalizeKey(string key) { key = this.NormalizePathSeparators(key); - return key.EndsWith(".xnb", StringComparison.InvariantCultureIgnoreCase) + return key.EndsWith(".xnb", StringComparison.OrdinalIgnoreCase) ? key.Substring(0, key.Length - 4) : this.NormalizeAssetNameForPlatform(key); } diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 47ef30d4..479ffa7f 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -229,7 +229,7 @@ namespace StardewModdingAPI.Framework public IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false) { // invalidate cache & track removed assets - IDictionary<string, Type> removedAssets = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase); + IDictionary<string, Type> removedAssets = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase); this.ContentManagerLock.InReadLock(() => { // cached assets @@ -263,7 +263,7 @@ namespace StardewModdingAPI.Framework if (removedAssets.Any()) { IDictionary<string, bool> propagated = this.CoreAssets.Propagate(this.MainContentManager, removedAssets.ToDictionary(p => p.Key, p => p.Value)); // use an intercepted content manager - this.Monitor.Log($"Invalidated {removedAssets.Count} asset names ({string.Join(", ", removedAssets.Keys.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase))}); propagated {propagated.Count(p => p.Value)} core assets.", LogLevel.Trace); + this.Monitor.Log($"Invalidated {removedAssets.Count} asset names ({string.Join(", ", removedAssets.Keys.OrderBy(p => p, StringComparer.OrdinalIgnoreCase))}); propagated {propagated.Count(p => p.Value)} core assets.", LogLevel.Trace); } else this.Monitor.Log("Invalidated 0 cache entries.", LogLevel.Trace); diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 36f2f650..a8de013a 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -87,7 +87,7 @@ namespace StardewModdingAPI.Framework.ContentManagers this.IsNamespaced = isNamespaced; // get asset data - this.LanguageCodes = this.GetKeyLocales().ToDictionary(p => p.Value, p => p.Key, StringComparer.InvariantCultureIgnoreCase); + this.LanguageCodes = this.GetKeyLocales().ToDictionary(p => p.Value, p => p.Key, StringComparer.OrdinalIgnoreCase); this.BaseDisposableReferences = reflection.GetField<List<IDisposable>>(this, "disposableAssets").GetValue(); } @@ -192,7 +192,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <returns>Returns the invalidated asset names and instances.</returns> public IDictionary<string, object> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false) { - IDictionary<string, object> removeAssets = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase); + IDictionary<string, object> removeAssets = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); this.Cache.Remove((key, asset) => { this.ParseCacheKey(key, out string assetName, out _); @@ -295,7 +295,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // handle localized key if (!string.IsNullOrWhiteSpace(cacheKey)) { - int lastSepIndex = cacheKey.LastIndexOf(".", StringComparison.InvariantCulture); + int lastSepIndex = cacheKey.LastIndexOf(".", StringComparison.Ordinal); if (lastSepIndex >= 0) { string suffix = cacheKey.Substring(lastSepIndex + 1, cacheKey.Length - lastSepIndex - 1); diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index eaaf0e6f..f20580e1 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -123,7 +123,7 @@ namespace StardewModdingAPI.Framework.ContentManagers base.OnLocaleChanged(); // find assets for which a translatable version was loaded - HashSet<string> removeAssetNames = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); + HashSet<string> removeAssetNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (string key in this.IsLocalizableLookup.Where(p => p.Value).Select(p => p.Key)) removeAssetNames.Add(this.TryParseExplicitLanguageAssetKey(key, out string assetName, out _) ? assetName : key); @@ -134,7 +134,7 @@ namespace StardewModdingAPI.Framework.ContentManagers || (this.TryParseExplicitLanguageAssetKey(key, out string assetName, out _) && removeAssetNames.Contains(assetName)) ) .Select(p => p.Key) - .OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase) + .OrderBy(p => p, StringComparer.OrdinalIgnoreCase) .ToArray(); if (invalidated.Any()) this.Monitor.Log($"Invalidated {invalidated.Length} asset names: {string.Join(", ", invalidated)} for locale change.", LogLevel.Trace); diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index cfda55b9..26ddb067 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -372,7 +372,7 @@ namespace StardewModdingAPI.Framework.ContentManagers break; } } - if (contentKey.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase)) + if (contentKey.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) contentKey = contentKey.Substring(0, contentKey.Length - 4); try diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs index 636b1979..11fae0b2 100644 --- a/src/SMAPI/Framework/DeprecationManager.cs +++ b/src/SMAPI/Framework/DeprecationManager.cs @@ -11,7 +11,7 @@ namespace StardewModdingAPI.Framework ** Fields *********/ /// <summary>The deprecations which have already been logged (as 'mod name::noun phrase::version').</summary> - private readonly HashSet<string> LoggedDeprecations = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); + private readonly HashSet<string> LoggedDeprecations = new HashSet<string>(StringComparer.OrdinalIgnoreCase); /// <summary>Encapsulates monitoring and logging for a given module.</summary> private readonly IMonitor Monitor; diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index 08ac1131..8b25a9b5 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -106,19 +106,20 @@ namespace StardewModdingAPI.Framework.Events // update cached data // (This is debounced here to avoid repeatedly sorting when handlers are added/removed, // and keeping a separate cached list allows changes during enumeration.) - if (this.CachedHandlers == null) + var handlers = this.CachedHandlers; // iterate local copy in case a mod adds/removes a handler while handling the event + if (handlers == null) { if (this.HasNewHandlers && this.Handlers.Any(p => p.Priority != EventPriority.Normal)) this.Handlers.Sort(); - this.CachedHandlers = this.Handlers.ToArray(); + this.CachedHandlers = handlers = this.Handlers.ToArray(); this.HasNewHandlers = false; } // raise event this.PerformanceMonitor.Track(this.EventName, () => { - foreach (ManagedEventHandler<TEventArgs> handler in this.CachedHandlers) + foreach (ManagedEventHandler<TEventArgs> handler in handlers) { if (match != null && !match(handler.SourceMod)) continue; diff --git a/src/SMAPI/Framework/GameVersion.cs b/src/SMAPI/Framework/GameVersion.cs index 07957624..3ed60920 100644 --- a/src/SMAPI/Framework/GameVersion.cs +++ b/src/SMAPI/Framework/GameVersion.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Framework ** Private methods *********/ /// <summary>A mapping of game to semantic versions.</summary> - private static readonly IDictionary<string, string> VersionMap = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) + private static readonly IDictionary<string, string> VersionMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["1.0"] = "1.0.0", ["1.01"] = "1.0.1", @@ -64,7 +64,7 @@ namespace StardewModdingAPI.Framework { foreach (var mapping in GameVersion.VersionMap) { - if (mapping.Value.Equals(semanticVersion, StringComparison.InvariantCultureIgnoreCase)) + if (mapping.Value.Equals(semanticVersion, StringComparison.OrdinalIgnoreCase)) return mapping.Key; } diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs index 79fb45b8..fde37d68 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs @@ -18,7 +18,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework public ISet<InstructionHandleResult> Flags { get; } = new HashSet<InstructionHandleResult>(); /// <summary>The brief noun phrases indicating what the handler matched for the current module.</summary> - public ISet<string> Phrases { get; } = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); + public ISet<string> Phrases { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase); /********* diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index 30701552..3ad1bd38 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -173,7 +173,7 @@ namespace StardewModdingAPI.Framework.ModLoading { return this.HasID() - && string.Equals(this.Manifest.UniqueID.Trim(), id?.Trim(), StringComparison.InvariantCultureIgnoreCase); + && string.Equals(this.Manifest.UniqueID.Trim(), id?.Trim(), StringComparison.OrdinalIgnoreCase); } /// <summary>Get the defined update keys.</summary> @@ -192,7 +192,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <param name="includeOptional">Whether to include optional dependencies.</param> public IEnumerable<string> GetRequiredModIds(bool includeOptional = false) { - HashSet<string> required = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); + HashSet<string> required = new HashSet<string>(StringComparer.OrdinalIgnoreCase); // yield dependencies if (this.Manifest?.Dependencies != null) diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index e73bc47d..8bbeb2a3 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -190,7 +190,7 @@ namespace StardewModdingAPI.Framework.ModLoading // validate IDs are unique { var duplicatesByID = mods - .GroupBy(mod => mod.Manifest?.UniqueID?.Trim(), mod => mod, StringComparer.InvariantCultureIgnoreCase) + .GroupBy(mod => mod.Manifest?.UniqueID?.Trim(), mod => mod, StringComparer.OrdinalIgnoreCase) .Where(p => p.Count() > 1); foreach (var group in duplicatesByID) { diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index a98d8c54..1c682f96 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -25,7 +25,7 @@ namespace StardewModdingAPI.Framework.Models }; /// <summary>The default values for <see cref="SuppressUpdateChecks"/>, to log changes if different.</summary> - private static readonly HashSet<string> DefaultSuppressUpdateChecks = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) + private static readonly HashSet<string> DefaultSuppressUpdateChecks = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "SMAPI.ConsoleCommands", "SMAPI.SaveBackup" @@ -84,7 +84,7 @@ namespace StardewModdingAPI.Framework.Models custom[pair.Key] = value; } - HashSet<string> curSuppressUpdateChecks = new HashSet<string>(this.SuppressUpdateChecks ?? new string[0], StringComparer.InvariantCultureIgnoreCase); + HashSet<string> curSuppressUpdateChecks = new HashSet<string>(this.SuppressUpdateChecks ?? new string[0], StringComparer.OrdinalIgnoreCase); if (SConfig.DefaultSuppressUpdateChecks.Count != curSuppressUpdateChecks.Count || SConfig.DefaultSuppressUpdateChecks.Any(p => !curSuppressUpdateChecks.Contains(p))) custom[nameof(this.SuppressUpdateChecks)] = "[" + string.Join(", ", this.SuppressUpdateChecks ?? new string[0]) + "]"; diff --git a/src/SMAPI/Framework/Networking/MultiplayerPeer.cs b/src/SMAPI/Framework/Networking/MultiplayerPeer.cs index b4e39379..6b45b04a 100644 --- a/src/SMAPI/Framework/Networking/MultiplayerPeer.cs +++ b/src/SMAPI/Framework/Networking/MultiplayerPeer.cs @@ -71,7 +71,7 @@ namespace StardewModdingAPI.Framework.Networking return null; id = id.Trim(); - return this.Mods.FirstOrDefault(mod => mod.ID != null && mod.ID.Equals(id, StringComparison.InvariantCultureIgnoreCase)); + return this.Mods.FirstOrDefault(mod => mod.ID != null && mod.ID.Equals(id, StringComparison.OrdinalIgnoreCase)); } /// <summary>Send a message to the given peer, bypassing the game's normal validation to allow messages before the connection is approved.</summary> diff --git a/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounterCollection.cs b/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounterCollection.cs index 0bb78c74..29a06794 100644 --- a/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounterCollection.cs +++ b/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounterCollection.cs @@ -189,7 +189,7 @@ namespace StardewModdingAPI.Framework.PerformanceMonitoring public void ResetSource(string source) { foreach (var i in this.PerformanceCounters) - if (i.Value.Source.Equals(source, StringComparison.InvariantCultureIgnoreCase)) + if (i.Value.Source.Equals(source, StringComparison.OrdinalIgnoreCase)) i.Value.Reset(); } diff --git a/src/SMAPI/Framework/PerformanceMonitoring/PerformanceMonitor.cs b/src/SMAPI/Framework/PerformanceMonitoring/PerformanceMonitor.cs index dfc4f31a..3f2608aa 100644 --- a/src/SMAPI/Framework/PerformanceMonitoring/PerformanceMonitor.cs +++ b/src/SMAPI/Framework/PerformanceMonitoring/PerformanceMonitor.cs @@ -23,7 +23,7 @@ namespace StardewModdingAPI.Framework.PerformanceMonitoring private readonly Stopwatch InvocationStopwatch = new Stopwatch(); /// <summary>The underlying performance counter collections.</summary> - private readonly IDictionary<string, PerformanceCounterCollection> Collections = new Dictionary<string, PerformanceCounterCollection>(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary<string, PerformanceCounterCollection> Collections = new Dictionary<string, PerformanceCounterCollection>(StringComparer.OrdinalIgnoreCase); /********* diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 2b04b1dc..72ef9095 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -451,7 +451,7 @@ namespace StardewModdingAPI.Framework { string[] looseFiles = new DirectoryInfo(this.ModsPath).GetFiles().Select(p => p.Name).ToArray(); if (looseFiles.Any()) - this.Monitor.Log($" Ignored loose files: {string.Join(", ", looseFiles.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase))}", LogLevel.Trace); + this.Monitor.Log($" Ignored loose files: {string.Join(", ", looseFiles.OrderBy(p => p, StringComparer.OrdinalIgnoreCase))}", LogLevel.Trace); } // load manifests @@ -650,7 +650,7 @@ namespace StardewModdingAPI.Framework { try { - HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.InvariantCultureIgnoreCase); + HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase); // prepare search model List<ModSearchEntryModel> searchMods = new List<ModSearchEntryModel>(); @@ -756,7 +756,7 @@ namespace StardewModdingAPI.Framework using (AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.Platform, this.Monitor, this.Settings.ParanoidWarnings)) { // init - HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.InvariantCultureIgnoreCase); + HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase); InterfaceProxyFactory proxyFactory = new InterfaceProxyFactory(); void LogSkip(IModMetadata mod, string errorPhrase, string errorDetails) { @@ -1103,8 +1103,8 @@ namespace StardewModdingAPI.Framework // find skipped dependencies KeyValuePair<IModMetadata, Tuple<string, string>>[] skippedDependencies; { - HashSet<string> skippedDependencyIds = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); - HashSet<string> skippedModIds = new HashSet<string>(from mod in skippedMods where mod.Key.HasID() select mod.Key.Manifest.UniqueID, StringComparer.InvariantCultureIgnoreCase); + HashSet<string> skippedDependencyIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase); + HashSet<string> skippedModIds = new HashSet<string>(from mod in skippedMods where mod.Key.HasID() select mod.Key.Manifest.UniqueID, StringComparer.OrdinalIgnoreCase); foreach (IModMetadata mod in skippedMods.Keys) { foreach (string requiredId in skippedModIds.Intersect(mod.GetRequiredModIds())) @@ -1351,8 +1351,8 @@ namespace StardewModdingAPI.Framework foreach (string locale in translations.Keys.ToArray()) { // handle duplicates - HashSet<string> keys = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); - HashSet<string> duplicateKeys = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); + HashSet<string> keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase); + HashSet<string> duplicateKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (string key in translations[locale].Keys.ToArray()) { if (!keys.Add(key)) @@ -1458,7 +1458,7 @@ namespace StardewModdingAPI.Framework foreach (FileInfo logFile in logsDir.EnumerateFiles()) { // skip non-SMAPI file - if (!logFile.Name.StartsWith(Constants.LogNamePrefix, StringComparison.InvariantCultureIgnoreCase)) + if (!logFile.Name.StartsWith(Constants.LogNamePrefix, StringComparison.OrdinalIgnoreCase)) continue; // skip crash log diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 4d310185..abb766f2 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -197,8 +197,14 @@ namespace StardewModdingAPI.Framework /// <summary>Load content when the game is launched.</summary> protected override void LoadContent() { + // load content base.LoadContent(); Game1.mapDisplayDevice = new SDisplayDevice(Game1.content, this.GraphicsDevice); + + // log GPU info +#if SMAPI_FOR_WINDOWS + this.Monitor.Log($"Running on GPU: {this.GraphicsDevice?.Adapter?.Description ?? "<unknown>"}"); +#endif } /// <summary>Initialize just before the game's first update tick.</summary> @@ -235,7 +241,7 @@ namespace StardewModdingAPI.Framework private void OnModMessageReceived(ModMessageModel message) { // get mod IDs to notify - HashSet<string> modIDs = new HashSet<string>(message.ToModIDs ?? this.ModRegistry.GetAll().Select(p => p.Manifest.UniqueID), StringComparer.InvariantCultureIgnoreCase); + HashSet<string> modIDs = new HashSet<string>(message.ToModIDs ?? this.ModRegistry.GetAll().Select(p => p.Manifest.UniqueID), StringComparer.OrdinalIgnoreCase); if (message.FromPlayerID == Game1.player?.UniqueMultiplayerID) modIDs.Remove(message.FromModID); // don't send a broadcast back to the sender diff --git a/src/SMAPI/Framework/SMultiplayer.cs b/src/SMAPI/Framework/SMultiplayer.cs index 8c444e45..f3b5e9b9 100644 --- a/src/SMAPI/Framework/SMultiplayer.cs +++ b/src/SMAPI/Framework/SMultiplayer.cs @@ -373,7 +373,7 @@ namespace StardewModdingAPI.Framework // filter by mod ID if (toModIDs != null) { - HashSet<string> sendToMods = new HashSet<string>(toModIDs, StringComparer.InvariantCultureIgnoreCase); + HashSet<string> sendToMods = new HashSet<string>(toModIDs, StringComparer.OrdinalIgnoreCase); if (sendToSelf && toModIDs.All(id => this.ModRegistry.Get(id) == null)) sendToSelf = false; diff --git a/src/SMAPI/Framework/Translator.cs b/src/SMAPI/Framework/Translator.cs index f2738633..11ec983b 100644 --- a/src/SMAPI/Framework/Translator.cs +++ b/src/SMAPI/Framework/Translator.cs @@ -12,7 +12,7 @@ namespace StardewModdingAPI.Framework ** Fields *********/ /// <summary>The translations for each locale.</summary> - private readonly IDictionary<string, IDictionary<string, string>> All = new Dictionary<string, IDictionary<string, string>>(StringComparer.InvariantCultureIgnoreCase); + private readonly IDictionary<string, IDictionary<string, string>> All = new Dictionary<string, IDictionary<string, string>>(StringComparer.OrdinalIgnoreCase); /// <summary>The translations for the current locale, with locale fallback taken into account.</summary> private IDictionary<string, Translation> ForLocale; @@ -45,7 +45,7 @@ namespace StardewModdingAPI.Framework this.Locale = locale.ToLower().Trim(); this.LocaleEnum = localeEnum; - this.ForLocale = new Dictionary<string, Translation>(StringComparer.InvariantCultureIgnoreCase); + this.ForLocale = new Dictionary<string, Translation>(StringComparer.OrdinalIgnoreCase); foreach (string next in this.GetRelevantLocales(this.Locale)) { // skip if locale not defined @@ -90,7 +90,7 @@ namespace StardewModdingAPI.Framework // reset translations this.All.Clear(); foreach (var pair in translations) - this.All[pair.Key] = new Dictionary<string, string>(pair.Value, StringComparer.InvariantCultureIgnoreCase); + this.All[pair.Key] = new Dictionary<string, string>(pair.Value, StringComparer.OrdinalIgnoreCase); // rebuild cache this.SetLocale(this.Locale, this.LocaleEnum); diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index fa6541cb..5c77bf66 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -84,7 +84,7 @@ namespace StardewModdingAPI.Metadata }); // reload assets - IDictionary<string, bool> propagated = assets.ToDictionary(p => p.Key, p => false, StringComparer.InvariantCultureIgnoreCase); + IDictionary<string, bool> propagated = assets.ToDictionary(p => p.Key, p => false, StringComparer.OrdinalIgnoreCase); foreach (var bucket in buckets) { switch (bucket.Key) @@ -779,7 +779,7 @@ namespace StardewModdingAPI.Metadata private void ReloadNpcSprites(LocalizedContentManager content, IEnumerable<string> keys, IDictionary<string, bool> propagated) { // get NPCs - HashSet<string> lookup = new HashSet<string>(keys, StringComparer.InvariantCultureIgnoreCase); + HashSet<string> lookup = new HashSet<string>(keys, StringComparer.OrdinalIgnoreCase); var characters = ( from npc in this.GetCharacters() @@ -806,7 +806,7 @@ namespace StardewModdingAPI.Metadata private void ReloadNpcPortraits(LocalizedContentManager content, IEnumerable<string> keys, IDictionary<string, bool> propagated) { // get NPCs - HashSet<string> lookup = new HashSet<string>(keys, StringComparer.InvariantCultureIgnoreCase); + HashSet<string> lookup = new HashSet<string>(keys, StringComparer.OrdinalIgnoreCase); var characters = ( from npc in this.GetCharacters() @@ -1031,7 +1031,7 @@ namespace StardewModdingAPI.Metadata if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(rawSubstring)) return false; - return key.StartsWith(this.NormalizeAssetNameIgnoringEmpty(rawSubstring), StringComparison.InvariantCultureIgnoreCase); + return key.StartsWith(this.NormalizeAssetNameIgnoringEmpty(rawSubstring), StringComparison.OrdinalIgnoreCase); } /// <summary>Get whether a normalized asset key is in the given folder.</summary> diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 9438f11e..6f3c8c55 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -67,7 +67,7 @@ namespace StardewModdingAPI AssemblyName name = new AssemblyName(e.Name); foreach (FileInfo dll in new DirectoryInfo(Program.DllSearchPath).EnumerateFiles("*.dll")) { - if (name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.InvariantCultureIgnoreCase)) + if (name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.OrdinalIgnoreCase)) return Assembly.LoadFrom(dll.FullName); } return null; diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 4af4527b..a3dbf52f 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -13,7 +13,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="LargeAddressAware" Version="1.0.4" /> + <PackageReference Include="LargeAddressAware" Version="1.0.5" /> <PackageReference Include="Mono.Cecil" Version="0.11.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Platonymous.TMXTile" Version="1.3.8" /> diff --git a/src/SMAPI/Translation.cs b/src/SMAPI/Translation.cs index 2196c8a5..149f6728 100644 --- a/src/SMAPI/Translation.cs +++ b/src/SMAPI/Translation.cs @@ -67,7 +67,7 @@ namespace StardewModdingAPI return this; // get dictionary of tokens - IDictionary<string, string> tokenLookup = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase); + IDictionary<string, string> tokenLookup = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); { // from dictionary if (tokens is IDictionary inputLookup) |