summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--build/common.targets3
-rw-r--r--docs/release-notes.md34
-rw-r--r--docs/technical/mod-package.md3
-rw-r--r--docs/technical/smapi.md9
-rw-r--r--src/SMAPI.Installer/InteractiveInstaller.cs12
-rw-r--r--src/SMAPI.Installer/assets/unix-launcher.sh34
-rw-r--r--src/SMAPI.ModBuildConfig/build/smapi.targets3
-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.Web/Controllers/LogParserController.cs16
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/LogParser.cs12
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs22
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/Models/ModType.cs15
-rw-r--r--src/SMAPI.Web/SMAPI.Web.csproj2
-rw-r--r--src/SMAPI.Web/ViewModels/LogParserModel.cs2
-rw-r--r--src/SMAPI.Web/Views/Index/Index.cshtml4
-rw-r--r--src/SMAPI.Web/Views/LogParser/Index.cshtml34
-rw-r--r--src/SMAPI.Web/wwwroot/Content/css/main.css2
-rw-r--r--src/SMAPI.Web/wwwroot/Content/images/pufferchick-cool.pngbin1099 -> 0 bytes
-rw-r--r--src/SMAPI.Web/wwwroot/Content/images/pufferchick-cool.svg1
-rw-r--r--src/SMAPI.Web/wwwroot/Content/images/pufferchick.pngbin831 -> 0 bytes
-rw-r--r--src/SMAPI.Web/wwwroot/Content/images/pufferchick.svg1
-rw-r--r--src/SMAPI.Web/wwwroot/Content/images/sidebar-bg.gifbin1104 -> 0 bytes
-rw-r--r--src/SMAPI.Web/wwwroot/Content/images/sidebar-bg.svg1
-rw-r--r--src/SMAPI.Web/wwwroot/Content/js/index.js4
-rw-r--r--src/SMAPI.Web/wwwroot/schemas/content-patcher.json4
-rw-r--r--src/SMAPI/Constants.cs8
-rw-r--r--src/SMAPI/Framework/Content/AssetInfo.cs4
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs4
-rw-r--r--src/SMAPI/Framework/Deprecations/DeprecationManager.cs2
-rw-r--r--src/SMAPI/Framework/ExitState.cs15
-rw-r--r--src/SMAPI/Framework/ModHelpers/CommandHelper.cs2
-rw-r--r--src/SMAPI/Framework/ModHelpers/ContentHelper.cs8
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModHelper.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs70
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs13
-rw-r--r--src/SMAPI/Framework/ModLoading/ModResolver.cs6
-rw-r--r--src/SMAPI/Framework/SCore.cs67
-rw-r--r--src/SMAPI/SMAPI.csproj3
-rw-r--r--src/SMAPI/Utilities/PerScreen.cs2
-rw-r--r--src/SMAPI/app.manifest41
-rw-r--r--src/SMAPI/i18n/tr.json2
44 files changed, 359 insertions, 123 deletions
diff --git a/.gitignore b/.gitignore
index 527ac6bb..a89ccd21 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,3 +34,6 @@ appsettings.Development.json
# Azure generated files
src/SMAPI.Web/Properties/PublishProfiles/*.pubxml
src/SMAPI.Web/Properties/ServiceDependencies/* - Web Deploy/
+
+# macOS
+.DS_Store
diff --git a/build/common.targets b/build/common.targets
index 230bef41..383e258b 100644
--- a/build/common.targets
+++ b/build/common.targets
@@ -7,12 +7,11 @@ repo. It imports the other MSBuild files as needed.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!--set general build properties -->
- <Version>3.15.1</Version>
+ <Version>3.16.0</Version>
<Product>SMAPI</Product>
<LangVersion>latest</LangVersion>
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
<DefineConstants>$(DefineConstants);SMAPI_DEPRECATED</DefineConstants>
- <DebugType>pdbonly</DebugType>
<DebugSymbols>true</DebugSymbols>
<!--enable nullable annotations, except in .NET Standard 2.0 where they aren't supported-->
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 8b258fb3..ca7d6e6e 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -4,9 +4,39 @@
<!--
## 4.0.0
* The installer no longer supports updating from SMAPI 2.11.3 or earlier (released in 2019).
- _If needed, you can update to SMAPI 3.15.0 first and then install to the latest version._
+ _If needed, you can update to SMAPI 3.16.0 first and then install the latest version._
-->
+## 3.16.0
+Released 22 August 2022 for Stardew Valley 1.5.6 or later. See [release highlights](https://www.patreon.com/posts/70797008).
+
+* For players:
+ * Added error message if mod files are detected directly under `Mods` (instead of each mod having its own subfolder).
+ * SMAPI now sets a success/error code when the game exits.
+ _This is used by your OS (like Windows) to decide whether to keep the console window open when the game ends._
+ * Fixed SMAPI on Windows applying different DPI awareness settings than the game (thanks to spacechase0!).
+ * Fixed Linux/macOS installer's color scheme question partly unreadable if the terminal background is dark.
+ * Fixed error message when a mod loads an invalid PNG file (thanks to atravita!).
+ * Fixed error message when a mod is duplicated, but one of the copies is also missing the DLL file. This now shows the duplicate-mod message instead of the missing-DLL message.
+ * Fixed macOS launcher using Terminal regardless of the system's default terminal (thanks to ishan!).
+ * Fixed best practices in Linux/macOS launcher scripts (thanks to ishan!).
+ * Improved translations. Thanks to KediDili (updated Turkish)!
+
+* For mod authors:
+ * While loading your mod, SMAPI now searches for indirect dependencies in your mod's folder (thanks to TehPers)! This mainly enables F# mods.
+ * **Raised deprecation message levels.**
+ _Deprecation warnings are now player-visible in the SMAPI console as faded `DEBUG` messages._
+ * Updated to Pintail 2.2.1 (see [changes](https://github.com/Nanoray-pl/Pintail/blob/master/docs/release-notes.md#221)).
+ * Switched SMAPI's `.pdb` files to the newer 'portable' format. This has no effect on mods.
+
+* For the web UI:
+ * Added log parser warning about performance of PyTK 1.23.0 or earlier.
+ * Converted images to SVG (thanks to ishan!).
+ * Updated log parser for the new update alert format in SMAPI 3.15.1.
+ * Updated the JSON validator/schema for Content Patcher 1.28.0.
+ * Fixed log parsing for invalid content packs.
+ * Fixed log parsing if a mod logged a null character.
+
## 3.15.1
Released 06 July 2022 for Stardew Valley 1.5.6 or later.
@@ -41,7 +71,7 @@ Released 17 June 2022 for Stardew Valley 1.5.6 or later. See [release highlights
* Updated dependencies:
* Harmony 2.2.1 (see changes in [2.2.0](https://github.com/pardeike/Harmony/releases/tag/v2.2.0.0) and [2.2.1](https://github.com/pardeike/Harmony/releases/tag/v2.2.1.0));
* Newtonsoft.Json 13.0.1 (see [changes](https://github.com/JamesNK/Newtonsoft.Json/releases/tag/13.0.1));
- * Pintail 2.2.0.
+ * Pintail 2.2.0 (see [changes](https://github.com/Nanoray-pl/Pintail/blob/master/docs/release-notes.md#220)).
* Removed transitional `UsePintail` option added in 3.14.0 (now always enabled).
* Fixed `onBehalfOf` arguments in the new content API being case-sensitive.
* Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players.
diff --git a/docs/technical/mod-package.md b/docs/technical/mod-package.md
index dd65a992..ca78be55 100644
--- a/docs/technical/mod-package.md
+++ b/docs/technical/mod-package.md
@@ -412,6 +412,9 @@ The NuGet package is generated automatically in `StardewModdingAPI.ModBuildConfi
when you compile it.
## Release notes
+### Upcoming release
+* Switched to the newer crossplatform `portable` debug symbols (thanks to lanturnalis!).
+
### 4.0.1
Released 14 April 2022.
diff --git a/docs/technical/smapi.md b/docs/technical/smapi.md
index 90990ee4..b8a1683b 100644
--- a/docs/technical/smapi.md
+++ b/docs/technical/smapi.md
@@ -33,14 +33,15 @@ argument | purpose
`--uninstall` | Preselects the uninstall action, skipping the prompt asking what the user wants to do.
`--game-path "path"` | Specifies the full path to the folder containing the Stardew Valley executable, skipping automatic detection and any prompt to choose a path. If the path is not valid, the installer displays an error.
-SMAPI itself recognises five arguments **on Windows only**, but these are intended for internal use
-or testing and may change without warning. On Linux/macOS, see _environment variables_ below.
+SMAPI itself recognises five arguments, but these are meant for internal use or testing, and might
+change without warning. **On Linux/macOS**, command-line arguments won't work; see _environment
+variables_ below instead.
argument | purpose
-------- | -------
`--developer-mode`<br />`--developer-mode-off` | Enable or disable features intended for mod developers. Currently this only makes `TRACE`-level messages appear in the console.
-`--no-terminal` | The SMAPI launcher won't try to open a terminal window, and SMAPI won't log anything to the console. (Messages will still be written to the log file.)
-`--use-current-shell` | The SMAPI launcher won't try to open a terminal window, but SMAPI will still log to the console. (Messages will still be written to the log file.)
+`--no-terminal` | SMAPI won't log anything to the console. On Linux/macOS only, this will also prevent the launch script from trying to open a terminal window. (Messages will still be written to the log file.)
+`--use-current-shell` | On Linux/macOS only, the launch script won't try to open a terminal window. All console output will be sent to the shell running the launch script.
`--mods-path` | The path to search for mods, if not the standard `Mods` folder. This can be a path relative to the game folder (like `--mods-path "Mods (test)"`) or an absolute path.
### Environment variables
diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs
index fd1a6047..d00a5df4 100644
--- a/src/SMAPI.Installer/InteractiveInstaller.cs
+++ b/src/SMAPI.Installer/InteractiveInstaller.cs
@@ -206,7 +206,7 @@ namespace StardewModdingApi.Installer
Console.WriteLine();
// handle choice
- string choice = this.InteractivelyChoose("Type 1 or 2, then press enter.", new[] { "1", "2" });
+ string choice = this.InteractivelyChoose("Type 1 or 2, then press enter.", new[] { "1", "2" }, printLine: Console.WriteLine);
switch (choice)
{
case "1":
@@ -629,22 +629,22 @@ namespace StardewModdingApi.Installer
}
/// <summary>Interactively ask the user to choose a value.</summary>
- /// <param name="print">A callback which prints a message to the console.</param>
+ /// <param name="printLine">A callback which prints a message to the console.</param>
/// <param name="message">The message to print.</param>
/// <param name="options">The allowed options (not case sensitive).</param>
/// <param name="indent">The indentation to prefix to output.</param>
- private string InteractivelyChoose(string message, string[] options, string indent = "", Action<string>? print = null)
+ private string InteractivelyChoose(string message, string[] options, string indent = "", Action<string>? printLine = null)
{
- print ??= this.PrintInfo;
+ printLine ??= this.PrintInfo;
while (true)
{
- print(indent + message);
+ printLine(indent + message);
Console.Write(indent);
string? input = Console.ReadLine()?.Trim().ToLowerInvariant();
if (input == null || !options.Contains(input))
{
- print($"{indent}That's not a valid option.");
+ printLine($"{indent}That's not a valid option.");
continue;
}
return input;
diff --git a/src/SMAPI.Installer/assets/unix-launcher.sh b/src/SMAPI.Installer/assets/unix-launcher.sh
index ae9624e7..778663d7 100644
--- a/src/SMAPI.Installer/assets/unix-launcher.sh
+++ b/src/SMAPI.Installer/assets/unix-launcher.sh
@@ -54,12 +54,12 @@ if [ "$(uname)" == "Darwin" ]; then
# https://stackoverflow.com/a/29511052/262123
if [ "$USE_CURRENT_SHELL" == "false" ]; then
echo "Reopening in the Terminal app..."
- echo '#!/bin/sh' > /tmp/open-smapi-terminal.sh
- echo "\"$0\" $@ --use-current-shell" >> /tmp/open-smapi-terminal.sh
- chmod +x /tmp/open-smapi-terminal.sh
- cat /tmp/open-smapi-terminal.sh
- open -W -a Terminal /tmp/open-smapi-terminal.sh
- rm /tmp/open-smapi-terminal.sh
+ echo '#!/bin/sh' > /tmp/open-smapi-terminal.command
+ echo "\"$0\" $@ --use-current-shell" >> /tmp/open-smapi-terminal.command
+ chmod +x /tmp/open-smapi-terminal.command
+ cat /tmp/open-smapi-terminal.command
+ open -W /tmp/open-smapi-terminal.command
+ rm /tmp/open-smapi-terminal.command
exit 0
fi
fi
@@ -71,8 +71,8 @@ fi
##########
# script must be run from the game folder
if [ ! -f "Stardew Valley.dll" ]; then
- echo "Oops! SMAPI must be placed in the Stardew Valley game folder.\nSee instructions: https://stardewvalleywiki.com/Modding:Player_Guide";
- read
+ printf "Oops! SMAPI must be placed in the Stardew Valley game folder.\nSee instructions: https://stardewvalleywiki.com/Modding:Player_Guide";
+ read -r
exit 1
fi
@@ -102,37 +102,39 @@ else
# find the true shell behind x-terminal-emulator
if [ "$TERMINAL_NAME" = "x-terminal-emulator" ]; then
- export TERMINAL_NAME="$(basename "$(readlink -f $(command -v x-terminal-emulator))")"
+ TERMINAL_NAME="$(basename "$(readlink -f "$(command -v x-terminal-emulator)")")"
+ export TERMINAL_NAME
fi
# run in selected terminal and account for quirks
- export TERMINAL_PATH="$(command -v $TERMINAL_NAME)"
- if [ -x $TERMINAL_PATH ]; then
+ TERMINAL_PATH="$(command -v "$TERMINAL_NAME")"
+ export TERMINAL_PATH
+ if [ -x "$TERMINAL_PATH" ]; then
case $TERMINAL_NAME in
terminal|termite)
# consumes only one argument after -e
# options containing space characters are unsupported
- exec $TERMINAL_NAME -e "env TERM=xterm $LAUNCH_FILE $@"
+ exec "$TERMINAL_NAME" -e "env TERM=xterm $LAUNCH_FILE $@"
;;
xterm|konsole|alacritty)
# consumes all arguments after -e
- exec $TERMINAL_NAME -e env TERM=xterm $LAUNCH_FILE "$@"
+ exec "$TERMINAL_NAME" -e env TERM=xterm $LAUNCH_FILE "$@"
;;
terminator|xfce4-terminal|mate-terminal)
# consumes all arguments after -x
- exec $TERMINAL_NAME -x env TERM=xterm $LAUNCH_FILE "$@"
+ exec "$TERMINAL_NAME" -x env TERM=xterm $LAUNCH_FILE "$@"
;;
gnome-terminal)
# consumes all arguments after --
- exec $TERMINAL_NAME -- env TERM=xterm $LAUNCH_FILE "$@"
+ exec "$TERMINAL_NAME" -- env TERM=xterm $LAUNCH_FILE "$@"
;;
kitty)
# consumes all trailing arguments
- exec $TERMINAL_NAME env TERM=xterm $LAUNCH_FILE "$@"
+ exec "$TERMINAL_NAME" env TERM=xterm $LAUNCH_FILE "$@"
;;
*)
diff --git a/src/SMAPI.ModBuildConfig/build/smapi.targets b/src/SMAPI.ModBuildConfig/build/smapi.targets
index b66ec27b..12619439 100644
--- a/src/SMAPI.ModBuildConfig/build/smapi.targets
+++ b/src/SMAPI.ModBuildConfig/build/smapi.targets
@@ -8,8 +8,7 @@
** Set build options
**********************************************-->
<PropertyGroup>
- <!-- include PDB file by default to enable line numbers in stack traces -->
- <DebugType>pdbonly</DebugType>
+ <!-- enable line numbers in stack traces -->
<DebugSymbols>true</DebugSymbols>
<!-- don't create the 'refs' folder (which isn't useful for mods) -->
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json
index 3c2dec19..adc45ec3 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.15.1",
+ "Version": "3.16.0",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
- "MinimumApiVersion": "3.15.1"
+ "MinimumApiVersion": "3.16.0"
}
diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json
index 28b4b149..3a3c9283 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.15.1",
+ "Version": "3.16.0",
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
"UniqueID": "SMAPI.ErrorHandler",
"EntryDll": "ErrorHandler.dll",
- "MinimumApiVersion": "3.15.1"
+ "MinimumApiVersion": "3.16.0"
}
diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json
index 1944575b..eb98aa32 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.15.1",
+ "Version": "3.16.0",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
- "MinimumApiVersion": "3.15.1"
+ "MinimumApiVersion": "3.16.0"
}
diff --git a/src/SMAPI.Web/Controllers/LogParserController.cs b/src/SMAPI.Web/Controllers/LogParserController.cs
index 33af5a81..a3bcf4c3 100644
--- a/src/SMAPI.Web/Controllers/LogParserController.cs
+++ b/src/SMAPI.Web/Controllers/LogParserController.cs
@@ -1,7 +1,9 @@
using System;
-using System.Linq;
+using System.Collections.Specialized;
+using System.IO;
using System.Text;
using System.Threading.Tasks;
+using System.Web;
using Microsoft.AspNetCore.Mvc;
using StardewModdingAPI.Toolkit.Utilities;
using StardewModdingAPI.Web.Framework;
@@ -87,9 +89,15 @@ namespace StardewModdingAPI.Web.Controllers
public async Task<ActionResult> PostAsync()
{
// get raw log text
- string? input = this.Request.Form["input"].FirstOrDefault();
- if (string.IsNullOrWhiteSpace(input))
- return this.View("Index", this.GetModel(null, uploadError: "The log file seems to be empty."));
+ // note: avoid this.Request.Form, which fails if any mod logged a null character.
+ string? input;
+ {
+ using StreamReader reader = new StreamReader(this.Request.Body);
+ NameValueCollection parsed = HttpUtility.ParseQueryString(await reader.ReadToEndAsync());
+ input = parsed["input"];
+ if (string.IsNullOrWhiteSpace(input))
+ return this.View("Index", this.GetModel(null, uploadError: "The log file seems to be empty."));
+ }
// upload log
UploadResult uploadResult = await this.Storage.SaveAsync(input);
diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
index 0efa62c5..18ba754c 100644
--- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
+++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
@@ -36,13 +36,13 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
private readonly Regex ContentPackListStartPattern = new(@"^Loaded \d+ content packs:$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>A regex pattern matching an entry in SMAPI's content pack list.</summary>
- private readonly Regex ContentPackListEntryPattern = new(@"^ (?<name>.+?) (?<version>[^\s]+)(?: by (?<author>[^\|]+))? \| for (?<for>[^\|]+)(?: \| (?<description>.+))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private readonly Regex ContentPackListEntryPattern = new(@"^ (?<name>.+?) (?<version>[^\s]+)(?: by (?<author>[^\|]+))? \| for (?<for>[^\|]*)(?: \| (?<description>.+))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>A regex pattern matching the start of SMAPI's mod update list.</summary>
private readonly Regex ModUpdateListStartPattern = new(@"^You can update \d+ mods?:$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>A regex pattern matching an entry in SMAPI's mod update list.</summary>
- private readonly Regex ModUpdateListEntryPattern = new(@"^ (?<name>.+) (?<version>[^\s]+): (?<link>.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private readonly Regex ModUpdateListEntryPattern = new(@"^ (?<name>.+) (?<version>[^\s]+): (?<link>[^\s]+)(?: \(you have [^\)]+\))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>A regex pattern matching SMAPI's update line.</summary>
private readonly Regex SmapiUpdatePattern = new(@"^You can update SMAPI to (?<version>[^\s]+): (?<link>.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
@@ -77,8 +77,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
};
// parse log messages
- LogModInfo smapiMod = new(name: "SMAPI", author: "Pathoschild", version: "", description: "", loaded: true, isMod: false);
- LogModInfo gameMod = new(name: "game", author: "", version: "", description: "", loaded: true, isMod: false);
+ LogModInfo smapiMod = new(ModType.Special, name: "SMAPI", author: "Pathoschild", version: "", description: "", loaded: true);
+ LogModInfo gameMod = new(ModType.Special, name: "game", author: "", version: "", description: "", loaded: true);
IDictionary<string, List<LogModInfo>> mods = new Dictionary<string, List<LogModInfo>>();
bool inModList = false;
bool inContentPackList = false;
@@ -133,7 +133,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
if (!mods.TryGetValue(name, out List<LogModInfo>? entries))
mods[name] = entries = new List<LogModInfo>();
- entries.Add(new LogModInfo(name: name, author: author, version: version, description: description, loaded: true));
+ entries.Add(new LogModInfo(ModType.CodeMod, name: name, author: author, version: version, description: description, loaded: true));
message.Section = LogSection.ModsList;
}
@@ -156,7 +156,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
if (!mods.TryGetValue(name, out List<LogModInfo>? entries))
mods[name] = entries = new List<LogModInfo>();
- entries.Add(new LogModInfo(name: name, author: author, version: version, description: description, contentPackFor: forMod, loaded: true));
+ entries.Add(new LogModInfo(ModType.ContentPack, name: name, author: author, version: version, description: description, contentPackFor: forMod, loaded: true));
message.Section = LogSection.ContentPackList;
}
diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs b/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs
index 557f08ff..c81942e4 100644
--- a/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs
+++ b/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs
@@ -48,21 +48,24 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
[MemberNotNullWhen(true, nameof(LogModInfo.UpdateVersion), nameof(LogModInfo.UpdateLink))]
public bool HasUpdate => this.UpdateVersion != null && this.Version != this.UpdateVersion;
+ /// <summary>The mod type.</summary>
+ public ModType ModType { get; }
+
/// <summary>Whether this is an actual mod (rather than a special entry for SMAPI or the game itself).</summary>
- public bool IsMod { get; }
+ public bool IsMod => this.ModType != ModType.Special;
/// <summary>Whether this is a C# code mod.</summary>
- public bool IsCodeMod { get; }
+ public bool IsCodeMod => this.ModType == ModType.CodeMod;
/// <summary>Whether this is a content pack for another mod.</summary>
- [MemberNotNullWhen(true, nameof(LogModInfo.ContentPackFor))]
- public bool IsContentPack { get; }
+ public bool IsContentPack => this.ModType == ModType.ContentPack;
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
+ /// <param name="modType">The mod type.</param>
/// <param name="name">The mod name.</param>
/// <param name="author">The mod author.</param>
/// <param name="version">The mod version.</param>
@@ -72,9 +75,9 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
/// <param name="contentPackFor">The name of the mod for which this is a content pack (if applicable).</param>
/// <param name="errors">The number of errors logged by this mod.</param>
/// <param name="loaded">Whether the mod was loaded into the game.</param>
- /// <param name="isMod">Whether this is an actual mod (instead of a special entry for SMAPI or the game).</param>
- public LogModInfo(string name, string author, string version, string description, string? updateVersion = null, string? updateLink = null, string? contentPackFor = null, int errors = 0, bool loaded = true, bool isMod = true)
+ public LogModInfo(ModType modType, string name, string author, string version, string description, string? updateVersion = null, string? updateLink = null, string? contentPackFor = null, int errors = 0, bool loaded = true)
{
+ this.ModType = modType;
this.Name = name;
this.Author = author;
this.Description = description;
@@ -84,13 +87,6 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models
this.Errors = errors;
this.Loaded = loaded;
- if (isMod)
- {
- this.IsMod = true;
- this.IsContentPack = !string.IsNullOrWhiteSpace(this.ContentPackFor);
- this.IsCodeMod = !this.IsContentPack;
- }
-
this.OverrideVersion(version);
}
diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/ModType.cs b/src/SMAPI.Web/Framework/LogParsing/Models/ModType.cs
new file mode 100644
index 00000000..363aaaec
--- /dev/null
+++ b/src/SMAPI.Web/Framework/LogParsing/Models/ModType.cs
@@ -0,0 +1,15 @@
+namespace StardewModdingAPI.Web.Framework.LogParsing.Models
+{
+ /// <summary>The type for a <see cref="LogModInfo"/> instance.</summary>
+ public enum ModType
+ {
+ /// <summary>A special non-mod entry (e.g. for SMAPI or the game itself).</summary>
+ Special,
+
+ /// <summary>A C# mod.</summary>
+ CodeMod,
+
+ /// <summary>A content pack loaded by a C# mod.</summary>
+ ContentPack
+ }
+}
diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj
index 0a460e9e..d26cb6f8 100644
--- a/src/SMAPI.Web/SMAPI.Web.csproj
+++ b/src/SMAPI.Web/SMAPI.Web.csproj
@@ -15,7 +15,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Azure.Storage.Blobs" Version="12.12.0" />
+ <PackageReference Include="Azure.Storage.Blobs" Version="12.13.0" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.29" />
<PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.43" />
diff --git a/src/SMAPI.Web/ViewModels/LogParserModel.cs b/src/SMAPI.Web/ViewModels/LogParserModel.cs
index c39a9b0a..34d0b4df 100644
--- a/src/SMAPI.Web/ViewModels/LogParserModel.cs
+++ b/src/SMAPI.Web/ViewModels/LogParserModel.cs
@@ -107,7 +107,7 @@ namespace StardewModdingAPI.Web.ViewModels
// group by mod
return mods
.Where(mod => mod.IsContentPack)
- .GroupBy(mod => mod.ContentPackFor!)
+ .GroupBy(mod => mod.ContentPackFor ?? "")
.ToDictionary(group => group.Key, group => group.ToArray());
}
diff --git a/src/SMAPI.Web/Views/Index/Index.cshtml b/src/SMAPI.Web/Views/Index/Index.cshtml
index acb8df78..d6472fcb 100644
--- a/src/SMAPI.Web/Views/Index/Index.cshtml
+++ b/src/SMAPI.Web/Views/Index/Index.cshtml
@@ -10,12 +10,12 @@
@section Head {
<link rel="stylesheet" href="~/Content/css/index.css" />
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1" crossorigin="anonymous"></script>
- <script src="~/Content/js/index.js"></script>
+ <script src="~/Content/js/index.js?r=20220708"></script>
}
<h1>
SMAPI
- <img id="pufferchick" src="Content/images/pufferchick.png" />
+ <img id="pufferchick" src="Content/images/pufferchick.svg" />
</h1>
<div id="blurb">
<p>The mod loader for Stardew Valley.</p>
diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml
index b824b768..f71c6ac1 100644
--- a/src/SMAPI.Web/Views/LogParser/Index.cshtml
+++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml
@@ -17,6 +17,7 @@
LogModInfo[] outdatedMods = log?.Mods.Where(mod => mod.HasUpdate).ToArray() ?? Array.Empty<LogModInfo>();
LogModInfo? errorHandler = log?.Mods.FirstOrDefault(p => p.IsCodeMod && p.Name == "Error Handler");
bool hasOlderErrorHandler = errorHandler?.GetParsedVersion() is not null && log?.ApiVersionParsed is not null && log.ApiVersionParsed.IsNewerThan(errorHandler.GetParsedVersion());
+ bool isPyTkCompatibilityMode = log?.ApiVersionParsed?.IsOlderThan("3.15.0") is false && log.Mods.Any(p => p.IsCodeMod && p.Name == "PyTK" && p.GetParsedVersion()?.IsOlderThan("1.23.1") is true);
// get filters
IDictionary<string, bool> defaultFilters = Enum
@@ -242,7 +243,7 @@ else if (log?.IsValid == true)
@if (log?.IsValid == true)
{
<div id="output">
- @if (outdatedMods.Any() || errorHandler is null || hasOlderErrorHandler)
+ @if (outdatedMods.Any() || errorHandler is null || hasOlderErrorHandler || isPyTkCompatibilityMode)
{
<h2>Suggested fixes</h2>
<ul id="fix-list">
@@ -254,6 +255,10 @@ else if (log?.IsValid == true)
{
<li>Your <strong>Error Handler</strong> mod is older than SMAPI. You may be missing some game/mod error fixes. You can <a href="https://stardewvalleywiki.com/Modding:Player_Guide#Install_SMAPI">reinstall SMAPI</a> to update it.</li>
}
+ @if (isPyTkCompatibilityMode)
+ {
+ <li>PyTK 1.23.0 or earlier isn't compatible with newer SMAPI performance optimizations. This may increase loading times or in-game lag.</li>
+ }
@if (outdatedMods.Any())
{
<li>
@@ -347,16 +352,31 @@ else if (log?.IsValid == true)
<span class="notice btn txt" v-on:click="toggleContentPacks">toggle content packs in list</span>
}
</caption>
- @foreach (var mod in log.Mods.Where(p => p.Loaded && !p.IsContentPack))
- {
- if (contentPacks == null || !contentPacks.TryGetValue(mod.Name, out LogModInfo[]? contentPackList))
- contentPackList = null;
+ @{
+ var modsWithContentPacks = log.Mods
+ .Where(mod => mod.Loaded && !mod.IsContentPack)
+ .Select(mod => (
+ Mod: mod,
+ ContentPacks: contentPacks?.TryGetValue(mod.Name, out LogModInfo[]? contentPackList) == true ? contentPackList : Array.Empty<LogModInfo>()
+ ))
+ .ToList();
+ if (contentPacks?.TryGetValue("", out LogModInfo[] invalidPacks) == true)
+ {
+ modsWithContentPacks.Add((
+ Mod: new LogModInfo(ModType.CodeMod, "<invalid content packs>", "", "", ""),
+ ContentPacks: invalidPacks
+ ));
+ }
+ }
+
+ @foreach ((LogModInfo mod, LogModInfo[] contentPackList) in modsWithContentPacks)
+ {
<tr v-on:click="toggleMod('@Model.GetSlug(mod.Name)')" class="mod-entry" v-bind:class="{ hidden: !showMods['@Model.GetSlug(mod.Name)'] }">
<td><input type="checkbox" v-bind:checked="showMods['@Model.GetSlug(mod.Name)']" v-bind:class="{ invisible: !anyModsHidden }" /></td>
<td>
<strong v-pre>@mod.Name</strong> @mod.Version
- @if (contentPackList != null)
+ @if (contentPackList.Any())
{
<div v-if="!hideContentPacks" class="content-packs">
@foreach (var contentPack in contentPackList)
@@ -369,7 +389,7 @@ else if (log?.IsValid == true)
</td>
<td>
@mod.Author
- @if (contentPackList != null)
+ @if (contentPackList.Any())
{
<div v-if="!hideContentPacks" class="content-packs">
@foreach (var contentPack in contentPackList)
diff --git a/src/SMAPI.Web/wwwroot/Content/css/main.css b/src/SMAPI.Web/wwwroot/Content/css/main.css
index a0a407d8..79de9c39 100644
--- a/src/SMAPI.Web/wwwroot/Content/css/main.css
+++ b/src/SMAPI.Web/wwwroot/Content/css/main.css
@@ -68,7 +68,7 @@ a {
margin-top: 3em;
min-height: 75%;
width: 12em;
- background: url("../images/sidebar-bg.gif") no-repeat top right;
+ background: url("../images/sidebar-bg.svg") no-repeat top right;
color: #666;
}
diff --git a/src/SMAPI.Web/wwwroot/Content/images/pufferchick-cool.png b/src/SMAPI.Web/wwwroot/Content/images/pufferchick-cool.png
deleted file mode 100644
index f359146c..00000000
--- a/src/SMAPI.Web/wwwroot/Content/images/pufferchick-cool.png
+++ /dev/null
Binary files differ
diff --git a/src/SMAPI.Web/wwwroot/Content/images/pufferchick-cool.svg b/src/SMAPI.Web/wwwroot/Content/images/pufferchick-cool.svg
new file mode 100644
index 00000000..2734c289
--- /dev/null
+++ b/src/SMAPI.Web/wwwroot/Content/images/pufferchick-cool.svg
@@ -0,0 +1 @@
+<svg width="56" height="56" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0 17.684h2.947v8.842H0v-8.842Zm29.474 32.421h2.947V56h-2.947v-5.895ZM18.667 29.474h2.947v5.894h-2.947v-5.894Zm16.701 20.631h2.948V56h-2.948v-5.895ZM2.948 14.737h5.894v2.947H2.947v-2.947Zm5.894 2.947h5.895v2.948H8.842v-2.948Zm0 23.58h5.895v2.947H8.842v-2.948ZM23.58 5.893h5.895v2.948H23.58V5.895Z" fill="#6B3407"/><path d="M14.737 41.263h5.895v2.948h-5.895v-2.948Zm29.474 0h5.894v2.948h-5.894v-2.948ZM32.42 23.58h5.895v2.947H32.42V23.58Z" fill="#8F4039"/><path d="M26.526 2.947h5.895v2.948h-5.895V2.947Z" fill="#FF4D1E"/><path d="M32.421 8.842h5.895v2.947H32.42V8.843Z" fill="#8F4039"/><path d="M23.579 47.158h14.737v2.947H23.579v-2.947Zm-1.965-11.79h11.79v2.948h-11.79v-2.948ZM20.632 0h11.79v2.947h-11.79V0Zm0 2.947h2.947v2.948h-2.947V2.947Zm11.79 0h2.946v2.948h-2.947V2.947Zm2.946 2.948h2.948v2.947h-2.948V5.895ZM2.948 26.526h2.947v2.948H2.947v-2.948Z" fill="#6B3407"/><path d="M2.947 23.579h2.948v2.947H2.947V23.58Zm2.948 2.947h2.947v2.948H5.895v-2.948Zm-2.948 5.895h2.948v2.947H2.947v-2.947Zm5.895 5.895h2.947v2.947H8.843v-2.947Zm9.825-2.948h2.947v2.948h-2.947v-2.948Zm14.736-2.947h2.948v2.947h-2.947v-2.947Z" fill="#9A4E0F"/><path d="M5.895 38.316h2.947v2.947H5.895v-2.947Zm15.719-8.842h2.947v2.947h-2.947v-2.947Z" fill="#6B3407"/><path d="M17.684 44.21h2.948v2.948h-2.948V44.21Zm5.895 0h2.947v2.948H23.58V44.21Zm20.632 0h2.947v2.948H44.21V44.21Zm5.894-5.894h2.948v2.947h-2.948v-2.947Zm-2.947-2.948h2.947v2.948h-2.947v-2.948Zm5.895-8.842H56v2.948h-2.947v-2.948Zm-2.948-2.947h2.948v2.947h-2.948V23.58ZM26.526 8.842h2.948v2.947h-2.948V8.843Z" fill="#8F4039"/><path d="M23.579 2.947h2.947v2.948H23.58V2.947Zm8.842 2.948h2.947v2.947h-2.947V5.895Z" fill="#C31E1E"/><path d="M29.474 5.895h2.947v2.947h-2.947V5.895Z" fill="#FF4D1E"/><path d="M23.579 11.79h2.947v2.947H23.58V11.79Z" fill="#8F4039"/><path d="M2.947 17.684h2.948v2.948H2.947v-2.948Z" fill="#FFE2AF"/><path d="M5.895 17.684h2.947v2.948H5.895v-2.948Zm-2.948 2.948h2.948v2.947H2.947v-2.947Zm8.843 0h2.947v2.947H11.79v-2.947Zm12.771 8.842h2.948v2.947H24.56v-2.947Zm8.843 0h2.947v2.947h-2.947v-2.947Zm0 5.894h2.947v2.948h-2.947v-2.948ZM8.841 26.526h2.947v5.895H8.843v-5.895Zm-5.895 2.948h2.948v2.947H2.947v-2.947Zm11.79 5.894h2.947v2.948h-2.947v-2.948Z" fill="#DEA159"/><path d="M14.737 38.316h2.947v2.947h-2.947v-2.947Zm5.895 2.947h2.947v2.948h-2.947v-2.948Zm23.579-8.842h2.947v2.947H44.21v-2.947Zm0-5.895h2.947v2.948H44.21v-2.948Zm-17.685 0h14.737v2.948H26.526v-2.948Zm0 11.79h5.895v2.947h-5.895v-2.947Zm5.895 2.947h8.842v2.948h-8.842v-2.948Zm2.947-2.947h8.843v2.947h-8.843v-2.947Zm2.948-2.948h8.842v2.948h-8.842v-2.948Zm0-11.79h8.842v2.948h-8.842V23.58Zm-2.948-8.841h8.843v2.947h-8.843v-2.947Zm-11.79 0h8.843v2.947H23.58v-2.947Zm0 8.842h8.843v2.947H23.58V23.58Zm5.896-5.895h11.79v5.895h-11.79v-5.895Zm6.877 14.737h4.912v2.947h-4.912v-2.947Zm0-2.947h13.754v2.947H36.351v-2.947ZM26.526 11.79h8.842v2.947h-8.842V11.79Z" fill="#FCD100"/><path d="M20.632 44.21h2.947v2.948h-2.947V44.21Z" fill="#DF8900"/><path d="M20.632 44.21h2.947v2.948h-2.947V44.21Zm20.631-2.947h2.948v2.948h-2.948v-2.948Zm2.948-2.947h2.947v2.947H44.21v-2.947Zm2.947-5.895h2.947v2.947h-2.947v-2.947Zm-5.895 0h2.948v2.947h-2.948v-2.947Zm2.948-14.737h2.947v2.948H44.21v-2.948Zm-8.843-5.895h2.948v2.948h-2.948V11.79Zm-14.736 2.948h2.947v2.947h-2.947v-2.947Zm-2.948 2.947h2.948v2.948h-2.948v-2.948Zm5.895 8.842h2.947v2.948H23.58v-2.948Zm8.842 11.79h2.947v2.947h-2.947v-2.947Zm14.737-14.737h2.947v5.895h-2.947v-5.895ZM18.667 38.316h7.86v2.947h-7.86v-2.947Zm4.912 2.947h8.842v2.948H23.58v-2.948Zm2.947 2.948h11.79v2.947h-11.79V44.21Zm-8.842-14.737h.983v11.79h-.983v-11.79Zm-2.947 2.947h2.947v2.947h-2.947v-2.947Zm0-8.842h2.947v2.947h-2.947V23.58Zm0 2.947h2.947v2.948h-2.947v-2.948Zm2.947 0h2.948v2.948h-2.948v-2.948Z" fill="#DF8900"/><path d="M41.263 44.21h2.948v2.948h-2.948V44.21Zm5.895-5.894h2.947v2.947h-2.947v-2.947Zm-32.421-8.842h2.947v2.947h-2.947v-2.947Zm5.895-5.895h2.947v5.895h-2.947v-5.895Zm0-11.79h2.947v2.948h-2.947V11.79Zm0 5.895h2.947v2.948h-2.947v-2.948Zm-2.948 2.948h2.948v5.894h-2.948v-5.894Zm32.421 5.894h2.948v2.948h-2.948v-2.948Zm-5.894-5.894h2.947v2.947H44.21v-2.947Zm-20.632-2.948h2.947v5.895H23.58v-5.895Z" fill="#FCD100"/><path d="M8.842 35.368h2.947v2.948H8.843v-2.948Zm-2.947-2.947h2.947v5.895H5.895V32.42Zm5.895-2.947h2.947v11.79H11.79v-11.79Z" fill="#DEA159"/><path d="M5.895 29.474h2.947v2.947H5.895v-2.947Zm2.947 2.947h2.947v2.947H8.843v-2.947Zm12.772 0h11.79v2.947h-11.79v-2.947Zm5.895-2.947h5.895v2.947h-5.895v-2.947ZM5.895 20.632h5.895v5.894H5.895v-5.894Zm5.895 2.947h2.947v5.895H11.79v-5.895Z" fill="#FFE2AF"/><path d="M20.632 8.842h2.947v2.947h-2.947V8.843Zm17.684 2.947h2.947v2.948h-2.947V11.79Z" fill="#8F4039"/><path d="M41.263 17.684h2.948v2.948h-2.948v-2.948Z" fill="#2F2000"/><path d="M41.263 20.632h2.948v2.947h-2.948v-2.947Z" fill="#F4F7AC"/><path d="M26.526 17.684h2.948v2.948h-2.948v-2.948Z" fill="#2F2000"/><path d="M26.526 20.632h2.948v2.947h-2.948v-2.947Z" fill="#F4F7AC"/><path d="M20.632 20.632h2.947v2.947h-2.947v-2.947Zm8.842-11.79h2.947v2.947h-2.947V8.843Zm2.947 5.895h2.947v2.947h-2.947v-2.947Zm8.842-2.947h2.948v2.947h-2.948V11.79Zm0 14.736h2.948v2.948h-2.948v-2.948Zm-4.912 8.842h1.965v2.948H36.35v-2.948Z" fill="#FFFF0C"/><path d="M41.263 8.842h2.948v2.947h-2.948V8.843Zm8.842 20.632h2.948v5.894h-2.948v-5.894Zm-2.947-11.79h2.947v5.895h-2.947v-5.895ZM44.21 11.79h2.947v5.895H44.21V11.79Zm-26.527 0h2.948v5.895h-2.948V11.79Zm-2.947 5.895h2.947v5.895h-2.947v-5.895Zm23.579 26.527h2.947v2.947h-2.947V44.21Zm2.947 2.947h2.948v2.947h-2.948v-2.947Z" fill="#8F4039"/><path d="M2.947 35.368h2.948v2.948H2.947v-2.948ZM0 29.474h2.947v5.894H0v-5.894Z" fill="#6B3407"/><path d="M14.832 16.702v1.52h2.25v1.522h1.522v1.521h1.52v1.522h1.521v1.52h7.163v-1.52h1.521v-1.522h1.521v-1.52h5.008v1.52h1.522v1.522h1.555v1.519h6.24v-1.519h1.87v-1.522h1.522v-1.52h1.52v-3.043H14.833Z" fill="#000"/><path d="M19.65 18.223v1.521h1.52v-1.521h-1.52Zm1.52 1.521v1.521h1.522v-1.52H21.17Zm1.522 0h1.52v-1.521h-1.521v1.521Zm1.52 0v1.521h1.521v-1.52h-1.52Zm13.692-1.521h1.521v1.521h1.52v-1.521h1.523v1.521h1.52v1.521h-1.52v-1.52h-1.522l-.001 1.52h-1.521l.001-1.52h-1.522l.001-1.522Z" fill="#436173"/><path d="M20.125 18.223v1.521h1.52v-1.521h-1.52Zm1.52 1.521v1.521h1.522v-1.52h-1.522Zm1.522 0h1.521v-1.521h-1.522l.001 1.521Zm1.521 0v1.521h1.52v-1.52h-1.52Zm13.692-1.521h1.52v1.521h1.521v-1.521h1.522v1.521h1.52v1.521h-1.52v-1.52h-1.522v1.52h-1.522l.001-1.52h-1.52v-1.522Z" fill="#667575"/></svg> \ No newline at end of file
diff --git a/src/SMAPI.Web/wwwroot/Content/images/pufferchick.png b/src/SMAPI.Web/wwwroot/Content/images/pufferchick.png
deleted file mode 100644
index 1de9cf47..00000000
--- a/src/SMAPI.Web/wwwroot/Content/images/pufferchick.png
+++ /dev/null
Binary files differ
diff --git a/src/SMAPI.Web/wwwroot/Content/images/pufferchick.svg b/src/SMAPI.Web/wwwroot/Content/images/pufferchick.svg
new file mode 100644
index 00000000..5e77e493
--- /dev/null
+++ b/src/SMAPI.Web/wwwroot/Content/images/pufferchick.svg
@@ -0,0 +1 @@
+<svg width="56" height="56" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0 17.684h2.947v8.842H0v-8.842Zm29.474 32.421h2.947V56h-2.947v-5.895ZM18.667 29.474h2.947v5.894h-2.947v-5.894Zm16.701 20.631h2.948V56h-2.948v-5.895ZM2.947 14.737h5.895v2.947H2.947v-2.947Zm5.895 2.947h5.895v2.948H8.842v-2.948Zm0 23.579h5.895v2.948H8.842v-2.948ZM23.579 5.895h5.895v2.947h-5.895V5.895Z" fill="#6B3407"/><path d="M14.737 41.263h5.895v2.948h-5.895v-2.948Zm29.473 0h5.895v2.948h-5.894v-2.948ZM32.421 23.579h5.895v2.947H32.42V23.58Z" fill="#8F4039"/><path d="M26.526 2.947h5.895v2.948h-5.895V2.947Z" fill="#FF4D1E"/><path d="M32.421 8.842h5.895v2.947H32.42V8.843Z" fill="#8F4039"/><path d="M23.579 47.158h14.737v2.947H23.579v-2.947Zm-1.965-11.79h11.79v2.948h-11.79v-2.948ZM20.632 0h11.79v2.947h-11.79V0Zm0 2.947h2.947v2.948h-2.947V2.947Zm11.789 0h2.947v2.948h-2.947V2.947Zm2.947 2.948h2.948v2.947h-2.948V5.895ZM2.947 26.526h2.948v2.948H2.947v-2.948Z" fill="#6B3407"/><path d="M2.947 23.579h2.948v2.947H2.947V23.58Zm2.948 2.947h2.947v2.948H5.895v-2.948Zm-2.948 5.895h2.948v2.947H2.947v-2.947Zm5.895 5.895h2.947v2.947H8.843v-2.947Zm9.825-2.948h2.947v2.948h-2.947v-2.948Zm14.737-2.947h2.947v2.947h-2.947v-2.947Z" fill="#9A4E0F"/><path d="M5.895 38.316h2.947v2.947H5.895v-2.947Zm15.719-8.842h2.947v2.947h-2.947v-2.947Z" fill="#6B3407"/><path d="M17.684 44.21h2.948v2.948h-2.948V44.21Zm5.895 0h2.947v2.948H23.58V44.21Zm20.631 0h2.948v2.948H44.21V44.21Zm5.895-5.894h2.948v2.947h-2.948v-2.947Zm-2.947-2.948h2.947v2.948h-2.947v-2.948Zm5.895-8.842H56v2.948h-2.947v-2.948Zm-2.948-2.947h2.948v2.947h-2.948V23.58ZM26.526 8.842h2.948v2.947h-2.948V8.843Z" fill="#8F4039"/><path d="M23.579 2.947h2.947v2.948H23.58V2.947Zm8.842 2.948h2.947v2.947h-2.947V5.895Z" fill="#C31E1E"/><path d="M29.474 5.895h2.947v2.947h-2.947V5.895Z" fill="#FF4D1E"/><path d="M23.579 11.79h2.947v2.947H23.58V11.79Z" fill="#8F4039"/><path d="M2.947 17.684h2.948v2.948H2.947v-2.948Z" fill="#FFE2AF"/><path d="M5.895 17.684h2.947v2.948H5.895v-2.948Zm-2.948 2.948h2.948v2.947H2.947v-2.947Zm8.843 0h2.947v2.947H11.79v-2.947Zm12.771 8.842h2.948v2.947H24.56v-2.947Zm8.843 0h2.947v2.947h-2.947v-2.947Zm0 5.894h2.947v2.948h-2.947v-2.948ZM8.842 26.526h2.947v5.895H8.843v-5.895Zm-5.895 2.948h2.948v2.947H2.947v-2.947Zm11.79 5.894h2.947v2.948h-2.947v-2.948Z" fill="#DEA159"/><path d="M14.737 38.316h2.947v2.947h-2.947v-2.947Zm5.895 2.947h2.947v2.948h-2.947v-2.948Zm23.578-8.842h2.948v2.947H44.21v-2.947Zm0-5.895h2.948v2.948H44.21v-2.948Zm-17.684 0h14.737v2.948H26.526v-2.948Zm0 11.79h5.895v2.947h-5.895v-2.947Zm5.895 2.947h8.842v2.948h-8.842v-2.948Z" fill="#FCD100"/><path d="M35.368 38.316h8.843v2.947h-8.843v-2.947Zm2.948-2.948h8.842v2.948h-8.842v-2.948Zm0-11.789h8.842v2.947h-8.842V23.58Zm-2.948-8.842h8.843v2.947h-8.843v-2.947Zm-11.789 0h8.842v2.947H23.58v-2.947Zm0 8.842h8.842v2.947H23.58V23.58Zm5.895-5.895h11.79v5.895h-11.79v-5.895Zm6.876 14.737h4.913v2.947h-4.912v-2.947Zm0-2.947h13.755v2.947H36.351v-2.947ZM26.526 11.79h8.842v2.947h-8.842V11.79Z" fill="#FCD100"/><path d="M20.632 44.21h2.947v2.948h-2.947V44.21Z" fill="#DF8900"/><path d="M20.632 44.21h2.947v2.948h-2.947V44.21Zm20.631-2.947h2.948v2.948h-2.948v-2.948Z" fill="#DF8900"/><path d="M44.21 38.316h2.948v2.947H44.21v-2.947Zm2.948-5.895h2.947v2.947h-2.947v-2.947Zm-5.895 0h2.948v2.947h-2.948v-2.947Zm2.947-14.737h2.948v2.948H44.21v-2.948Zm-8.842-5.894h2.948v2.947h-2.948V11.79Zm-14.736 2.947h2.947v2.947h-2.947v-2.947Zm-2.948 2.947h2.948v2.948h-2.948v-2.948Zm5.895 8.842h2.947v2.948H23.58v-2.948Zm8.842 11.79h2.947v2.947h-2.947v-2.947Zm14.737-14.737h2.947v5.895h-2.947v-5.895ZM18.667 38.316h7.86v2.947h-7.86v-2.947Z" fill="#DF8900"/><path d="M23.579 41.263h8.842v2.948H23.58v-2.948Z" fill="#DF8900"/><path d="M26.526 44.21h11.79v2.948h-11.79V44.21Zm-8.842-14.736h.983v11.79h-.983v-11.79Zm-2.947 2.947h2.947v2.947h-2.947v-2.947Zm0-8.842h2.947v2.947h-2.947V23.58Zm0 2.947h2.947v2.948h-2.947v-2.948Z" fill="#DF8900"/><path d="M17.684 26.526h2.948v2.948h-2.948v-2.948Z" fill="#DF8900"/><path d="M41.263 44.21h2.948v2.948h-2.948V44.21Zm5.895-5.894h2.947v2.947h-2.947v-2.947Zm-32.421-8.842h2.947v2.947h-2.947v-2.947Zm5.895-5.895h2.947v5.895h-2.947v-5.895Zm0-11.789h2.947v2.947h-2.947V11.79Zm0 5.894h2.947v2.948h-2.947v-2.948Zm-2.948 2.948h2.948v5.894h-2.948v-5.894Zm32.421 5.894h2.948v2.948h-2.948v-2.948Zm-5.895-5.894h2.948v2.947H44.21v-2.947Zm-20.631-2.948h2.947v5.895H23.58v-5.895Z" fill="#FCD100"/><path d="M8.842 35.368h2.947v2.948H8.843v-2.948Zm-2.947-2.947h2.947v5.895H5.895V32.42Zm5.895-2.947h2.947v11.79H11.79v-11.79Z" fill="#DEA159"/><path d="M5.895 29.474h2.947v2.947H5.895v-2.947Zm2.947 2.947h2.947v2.947H8.843v-2.947Zm12.772 0h11.79v2.947h-11.79v-2.947Zm5.895-2.947h5.895v2.947h-5.895v-2.947ZM5.895 20.632h5.895v5.894H5.895v-5.894Zm5.895 2.947h2.947v5.895H11.79v-5.895Z" fill="#FFE2AF"/><path d="M20.632 8.842h2.947v2.947h-2.947V8.843Zm17.684 2.948h2.947v2.947h-2.947V11.79Z" fill="#8F4039"/><path d="M41.263 17.684h2.948v2.948h-2.948v-2.948Z" fill="#2F2000"/><path d="M41.263 20.632h2.948v2.947h-2.948v-2.947Z" fill="#F4F7AC"/><path d="M26.526 17.684h2.948v2.948h-2.948v-2.948Z" fill="#2F2000"/><path d="M26.526 20.632h2.948v2.947h-2.948v-2.947Z" fill="#F4F7AC"/><path d="M20.632 20.632h2.947v2.947h-2.947v-2.947Zm8.842-11.79h2.947v2.947h-2.947V8.843Zm2.947 5.895h2.947v2.947h-2.947v-2.947Zm8.842-2.947h2.948v2.947h-2.948V11.79Zm0 14.736h2.948v2.948h-2.948v-2.948Zm-4.913 8.842h1.966v2.948H36.35v-2.948Z" fill="#FFFF0C"/><path d="M41.263 8.842h2.948v2.947h-2.948V8.843Zm8.842 20.632h2.948v5.894h-2.948v-5.894Zm-2.947-11.79h2.947v5.895h-2.947v-5.895ZM44.21 11.79h2.948v5.894H44.21V11.79Zm-26.526 0h2.948v5.894h-2.948V11.79Zm-2.947 5.894h2.947v5.895h-2.947v-5.895ZM38.316 44.21h2.947v2.948h-2.947V44.21Zm2.947 2.948h2.948v2.947h-2.948v-2.947Z" fill="#8F4039"/><path d="M2.947 35.368h2.948v2.948H2.947v-2.948ZM0 29.474h2.947v5.894H0v-5.894Z" fill="#6B3407"/></svg> \ No newline at end of file
diff --git a/src/SMAPI.Web/wwwroot/Content/images/sidebar-bg.gif b/src/SMAPI.Web/wwwroot/Content/images/sidebar-bg.gif
deleted file mode 100644
index 48e9af5a..00000000
--- a/src/SMAPI.Web/wwwroot/Content/images/sidebar-bg.gif
+++ /dev/null
Binary files differ
diff --git a/src/SMAPI.Web/wwwroot/Content/images/sidebar-bg.svg b/src/SMAPI.Web/wwwroot/Content/images/sidebar-bg.svg
new file mode 100644
index 00000000..f0613272
--- /dev/null
+++ b/src/SMAPI.Web/wwwroot/Content/images/sidebar-bg.svg
@@ -0,0 +1 @@
+<svg width="63" height="126" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><path d="M63.002 30.06c-18.36 0-33.297 14.778-33.297 32.94 0 18.163 14.938 32.94 33.297 32.94 18.359 0 33.295-14.776 33.295-32.94 0-18.162-14.936-32.94-33.295-32.94ZM115.603 63c0-1.674-.088-3.327-.244-4.96L126 53.78a61.838 61.838 0 0 0-2.182-9.52l-11.451.73a51.898 51.898 0 0 0-4.358-8.944l7.708-8.395a63.075 63.075 0 0 0-6.159-7.63L99.556 25.6a52.684 52.684 0 0 0-7.83-6.186l3.27-10.892a63.833 63.833 0 0 0-8.886-4.243l-6.57 9.323a52.763 52.763 0 0 0-9.774-2.198L67.934.187a65.106 65.106 0 0 0-9.87 0l-1.83 11.216A52.795 52.795 0 0 0 46.46 13.6l-6.568-9.323a63.782 63.782 0 0 0-8.886 4.243l3.271 10.892a52.659 52.659 0 0 0-7.832 6.186l-10.001-5.576a63.235 63.235 0 0 0-6.16 7.629l7.71 8.395a51.7 51.7 0 0 0-4.359 8.945l-11.452-.73A62.05 62.05 0 0 0 0 53.78l10.642 4.258a51.832 51.832 0 0 0-.245 4.96c0 1.673.088 3.326.245 4.96L0 72.217a62.178 62.178 0 0 0 2.182 9.52l11.452-.731a51.65 51.65 0 0 0 4.36 8.944l-7.711 8.396a63.311 63.311 0 0 0 6.16 7.629l10-5.576a52.731 52.731 0 0 0 7.833 6.186l-3.271 10.892a63.789 63.789 0 0 0 8.886 4.242l6.568-9.321a52.772 52.772 0 0 0 9.775 2.198l1.83 11.216c3.285.248 6.585.248 9.87 0l1.832-11.216a52.76 52.76 0 0 0 9.774-2.198l6.569 9.321a63.767 63.767 0 0 0 8.886-4.242l-3.269-10.892a52.904 52.904 0 0 0 7.83-6.186l10.002 5.576a63.1 63.1 0 0 0 6.159-7.63l-7.711-8.395a51.625 51.625 0 0 0 4.361-8.944l11.451.73a61.714 61.714 0 0 0 2.182-9.52l-10.641-4.258c.156-1.633.244-3.286.244-4.96V63Zm-52.601 38.253c-21.322 0-38.67-17.16-38.67-38.252 0-21.093 17.348-38.253 38.67-38.253 21.321 0 38.668 17.16 38.668 38.253s-17.347 38.252-38.668 38.252Z" fill="#C4C4C4"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h63v126H0z"/></clipPath></defs></svg> \ No newline at end of file
diff --git a/src/SMAPI.Web/wwwroot/Content/js/index.js b/src/SMAPI.Web/wwwroot/Content/js/index.js
index d0734b02..04db5c2f 100644
--- a/src/SMAPI.Web/wwwroot/Content/js/index.js
+++ b/src/SMAPI.Web/wwwroot/Content/js/index.js
@@ -3,10 +3,10 @@ $(document).ready(function () {
var pufferchick = $("#pufferchick");
$(".cta-dropdown").hover(
function () {
- pufferchick.attr("src", "Content/images/pufferchick-cool.png");
+ pufferchick.attr("src", "Content/images/pufferchick-cool.svg");
},
function () {
- pufferchick.attr("src", "Content/images/pufferchick.png");
+ pufferchick.attr("src", "Content/images/pufferchick.svg");
}
);
diff --git a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json
index 631fbc63..a00403c0 100644
--- a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json
+++ b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json
@@ -14,9 +14,9 @@
"title": "Format version",
"description": "The format version. You should always use the latest version to enable the latest features, avoid obsolete behavior, and reduce load times.",
"type": "string",
- "pattern": "^1\\.27\\.[0-9]+$",
+ "pattern": "^1\\.28\\.[0-9]+$",
"@errorMessages": {
- "pattern": "Incorrect value '@value'. You should always use the latest format version (currently 1.27.0) to enable the latest features, avoid obsolete behavior, and reduce load times."
+ "pattern": "Incorrect value '@value'. You should always use the latest format version (currently 1.28.0) to enable the latest features, avoid obsolete behavior, and reduce load times."
}
},
"ConfigSchema": {
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index d733e61e..c79a72ef 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -52,7 +52,7 @@ namespace StardewModdingAPI
internal static int? LogScreenId { get; set; }
/// <summary>SMAPI's current raw semantic version.</summary>
- internal static string RawApiVersion = "3.15.1";
+ internal static string RawApiVersion = "3.16.0";
}
/// <summary>Contains SMAPI's constants and assumptions.</summary>
@@ -90,7 +90,7 @@ namespace StardewModdingAPI
source: null,
nounPhrase: $"{nameof(Constants)}.{nameof(Constants.ExecutionPath)}",
version: "3.14.0",
- severity: DeprecationLevel.Notice
+ severity: DeprecationLevel.Info
);
return Constants.GamePath;
@@ -244,8 +244,8 @@ namespace StardewModdingAPI
internal static void ConfigureAssemblyResolver(AssemblyDefinitionResolver resolver)
{
// add search paths
- resolver.AddSearchDirectory(Constants.GamePath);
- resolver.AddSearchDirectory(Constants.InternalFilesPath);
+ resolver.TryAddSearchDirectory(Constants.GamePath);
+ resolver.TryAddSearchDirectory(Constants.InternalFilesPath);
// add SMAPI explicitly
// Normally this would be handled automatically by the search paths, but for some reason there's a specific
diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs
index 43feed27..af000300 100644
--- a/src/SMAPI/Framework/Content/AssetInfo.cs
+++ b/src/SMAPI/Framework/Content/AssetInfo.cs
@@ -45,7 +45,7 @@ namespace StardewModdingAPI.Framework.Content
source: null,
nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetName)}",
version: "3.14.0",
- severity: DeprecationLevel.Notice,
+ severity: DeprecationLevel.Info,
unlessStackIncludes: new[]
{
$"{typeof(AssetInterceptorChange).FullName}.{nameof(AssetInterceptorChange.CanIntercept)}",
@@ -84,7 +84,7 @@ namespace StardewModdingAPI.Framework.Content
source: null,
nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetNameEquals)}",
version: "3.14.0",
- severity: DeprecationLevel.Notice,
+ severity: DeprecationLevel.Info,
unlessStackIncludes: new[]
{
$"{typeof(AssetInterceptorChange).FullName}.{nameof(AssetInterceptorChange.CanIntercept)}",
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index f3cf05d9..8ecbc4cc 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -254,6 +254,10 @@ namespace StardewModdingAPI.Framework.ContentManagers
{
using FileStream stream = File.OpenRead(file.FullName);
using SKBitmap bitmap = SKBitmap.Decode(stream);
+
+ if (bitmap is null)
+ throw new InvalidDataException($"Failed to load {file.FullName}. This doesn't seem to be a valid PNG image.");
+
rawPixels = SKPMColor.PreMultiply(bitmap.Pixels);
width = bitmap.Width;
height = bitmap.Height;
diff --git a/src/SMAPI/Framework/Deprecations/DeprecationManager.cs b/src/SMAPI/Framework/Deprecations/DeprecationManager.cs
index 4597a764..f58f085e 100644
--- a/src/SMAPI/Framework/Deprecations/DeprecationManager.cs
+++ b/src/SMAPI/Framework/Deprecations/DeprecationManager.cs
@@ -124,7 +124,7 @@ namespace StardewModdingAPI.Framework.Deprecations
}
// log message
- if (level == LogLevel.Trace)
+ if (level is LogLevel.Trace or LogLevel.Debug)
{
if (warning.LogStackTrace)
message += $"\n{this.GetSimplifiedStackTrace(warning.StackTrace, warning.Mod)}";
diff --git a/src/SMAPI/Framework/ExitState.cs b/src/SMAPI/Framework/ExitState.cs
new file mode 100644
index 00000000..bc022d91
--- /dev/null
+++ b/src/SMAPI/Framework/ExitState.cs
@@ -0,0 +1,15 @@
+namespace StardewModdingAPI.Framework
+{
+ /// <summary>The SMAPI exit state.</summary>
+ internal enum ExitState
+ {
+ /// <summary>SMAPI didn't trigger an explicit exit.</summary>
+ None,
+
+ /// <summary>The game is exiting normally.</summary>
+ GameExit,
+
+ /// <summary>The game is exiting due to an error.</summary>
+ Crash
+ }
+}
diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs
index 21435f62..b7d4861f 100644
--- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs
@@ -43,7 +43,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
source: this.Mod,
nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.ConsoleCommands)}.{nameof(ICommandHelper.Trigger)}",
version: "3.8.1",
- severity: DeprecationLevel.Notice
+ severity: DeprecationLevel.Info
);
return this.CommandManager.Trigger(name, arguments);
diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
index 9992cb52..0a1633bf 100644
--- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
@@ -62,7 +62,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
source: this.Mod,
nounPhrase: $"{nameof(IContentHelper)}.{nameof(IContentHelper.AssetLoaders)}",
version: "3.14.0",
- severity: DeprecationLevel.Notice
+ severity: DeprecationLevel.Info
);
return this.ObservableAssetLoaders;
@@ -78,7 +78,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
source: this.Mod,
nounPhrase: $"{nameof(IContentHelper)}.{nameof(IContentHelper.AssetEditors)}",
version: "3.14.0",
- severity: DeprecationLevel.Notice
+ severity: DeprecationLevel.Info
);
return this.ObservableAssetEditors;
@@ -126,7 +126,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
this.Mod,
"loading assets from the Content folder with a .xnb file extension",
"3.14.0",
- DeprecationLevel.Notice
+ DeprecationLevel.Info
);
}
@@ -150,7 +150,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
this.Mod,
"loading XNB files from the mod folder without the .xnb file extension",
"3.14.0",
- DeprecationLevel.Notice
+ DeprecationLevel.Info
);
return data;
}
diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs
index caa66bad..1cdd8536 100644
--- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs
@@ -41,7 +41,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
source: this.Mod,
nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.Content)}",
version: "3.14.0",
- severity: DeprecationLevel.Notice
+ severity: DeprecationLevel.Info
);
return this.ContentImpl;
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs
index b3378ad1..5a850255 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs
@@ -4,18 +4,31 @@ using Mono.Cecil;
namespace StardewModdingAPI.Framework.ModLoading
{
/// <summary>A minimal assembly definition resolver which resolves references to known assemblies.</summary>
- internal class AssemblyDefinitionResolver : DefaultAssemblyResolver
+ internal class AssemblyDefinitionResolver : IAssemblyResolver
{
/*********
** Fields
*********/
+ /// <summary>The underlying assembly resolver.</summary>
+ private readonly DefaultAssemblyResolverWrapper Resolver = new();
+
/// <summary>The known assemblies.</summary>
private readonly IDictionary<string, AssemblyDefinition> Lookup = new Dictionary<string, AssemblyDefinition>();
+ /// <summary>The directory paths to search for assemblies.</summary>
+ private readonly HashSet<string> SearchPaths = new();
+
/*********
** Public methods
*********/
+ /// <summary>Construct an instance.</summary>
+ public AssemblyDefinitionResolver()
+ {
+ foreach (string path in this.Resolver.GetSearchDirectories())
+ this.SearchPaths.Add(path);
+ }
+
/// <summary>Add known assemblies to the resolver.</summary>
/// <param name="assemblies">The known assemblies.</param>
public void Add(params AssemblyDefinition[] assemblies)
@@ -29,7 +42,7 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <param name="names">The assembly names for which it should be returned.</param>
public void AddWithExplicitNames(AssemblyDefinition assembly, params string[] names)
{
- this.RegisterAssembly(assembly);
+ this.Resolver.AddAssembly(assembly);
foreach (string name in names)
this.Lookup[name] = assembly;
}
@@ -37,18 +50,52 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <summary>Resolve an assembly reference.</summary>
/// <param name="name">The assembly name.</param>
/// <exception cref="AssemblyResolutionException">The assembly can't be resolved.</exception>
- public override AssemblyDefinition Resolve(AssemblyNameReference name)
+ public AssemblyDefinition Resolve(AssemblyNameReference name)
{
- return this.ResolveName(name.Name) ?? base.Resolve(name);
+ return this.ResolveName(name.Name) ?? this.Resolver.Resolve(name);
}
/// <summary>Resolve an assembly reference.</summary>
/// <param name="name">The assembly name.</param>
/// <param name="parameters">The assembly reader parameters.</param>
/// <exception cref="AssemblyResolutionException">The assembly can't be resolved.</exception>
- public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
+ public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
{
- return this.ResolveName(name.Name) ?? base.Resolve(name, parameters);
+ return this.ResolveName(name.Name) ?? this.Resolver.Resolve(name, parameters);
+ }
+
+ /// <summary>Add a directory path to search for assemblies, if it's non-null and not already added.</summary>
+ /// <param name="path">The path to search.</param>
+ /// <returns>Returns whether the path was successfully added.</returns>
+ public bool TryAddSearchDirectory(string? path)
+ {
+ if (path is not null && this.SearchPaths.Add(path))
+ {
+ this.Resolver.AddSearchDirectory(path);
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>Remove a directory path to search for assemblies, if it's non-null.</summary>
+ /// <param name="path">The path to remove.</param>
+ /// <returns>Returns whether the path was in the list and removed.</returns>
+ public bool RemoveSearchDirectory(string? path)
+ {
+ if (path is not null && this.SearchPaths.Remove(path))
+ {
+ this.Resolver.RemoveSearchDirectory(path);
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ this.Resolver.Dispose();
}
@@ -63,5 +110,16 @@ namespace StardewModdingAPI.Framework.ModLoading
? match
: null;
}
+
+ /// <summary>An internal wrapper around <see cref="DefaultAssemblyResolver"/> to allow access to its protected methods.</summary>
+ private class DefaultAssemblyResolverWrapper : DefaultAssemblyResolver
+ {
+ /// <summary>Add an assembly to the resolver.</summary>
+ /// <param name="assembly">The assembly to add.</param>
+ public void AddAssembly(AssemblyDefinition assembly)
+ {
+ this.RegisterAssembly(assembly);
+ }
+ }
}
}
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
index eb940c41..01037870 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -264,8 +264,15 @@ namespace StardewModdingAPI.Framework.ModLoading
if (!file.Exists)
yield break; // not a local assembly
+ // add the assembly's directory temporarily if needed
+ // this is needed by F# mods which bundle FSharp.Core.dll, for example
+ string? temporarySearchDir = null;
+ if (this.AssemblyDefinitionResolver.TryAddSearchDirectory(file.DirectoryName))
+ temporarySearchDir = file.DirectoryName;
+
// read assembly
AssemblyDefinition assembly;
+ try
{
byte[] assemblyBytes = File.ReadAllBytes(file.FullName);
Stream readStream = this.TrackForDisposal(new MemoryStream(assemblyBytes));
@@ -286,6 +293,12 @@ namespace StardewModdingAPI.Framework.ModLoading
assembly = this.TrackForDisposal(AssemblyDefinition.ReadAssembly(readStream, new ReaderParameters(ReadingMode.Immediate) { AssemblyResolver = assemblyResolver, InMemory = true }));
}
}
+ finally
+ {
+ // clean up temporary search directory
+ if (temporarySearchDir is not null)
+ this.AssemblyDefinitionResolver.RemoveSearchDirectory(temporarySearchDir);
+ }
// skip if already visited
if (visitedAssemblyNames.Contains(assembly.Name.Name))
diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs
index abc46d47..c5648c74 100644
--- a/src/SMAPI/Framework/ModLoading/ModResolver.cs
+++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs
@@ -47,7 +47,7 @@ namespace StardewModdingAPI.Framework.ModLoading
IModMetadata metadata = new ModMetadata(folder.DisplayName, folder.Directory.FullName, rootPath, manifest, dataRecord, isIgnored: shouldIgnore);
if (shouldIgnore)
metadata.SetStatus(status, ModFailReason.DisabledByDotConvention, "disabled by dot convention");
- else
+ else if (status == ModMetadataStatus.Failed)
metadata.SetStatus(status, ModFailReason.InvalidManifest, folder.ManifestParseErrorText);
yield return metadata;
@@ -223,8 +223,8 @@ namespace StardewModdingAPI.Framework.ModLoading
{
foreach (IModMetadata mod in group)
{
- if (mod.Status == ModMetadataStatus.Failed)
- continue; // don't replace metadata error
+ if (mod.Status == ModMetadataStatus.Failed && mod.FailReason != ModFailReason.InvalidManifest)
+ continue;
string folderList = string.Join(", ", group.Select(p => p.GetRelativePathWithRoot()).OrderBy(p => p));
mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.Duplicate, $"you have multiple copies of this mod installed. To fix this, delete these folders and reinstall the mod: {folderList}.");
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 46d65f6a..0f86ed6b 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -50,6 +49,7 @@ using StardewModdingAPI.Utilities;
using StardewValley;
using StardewValley.Menus;
using StardewValley.Objects;
+using StardewValley.SDKs;
using xTile.Display;
using LanguageCode = StardewValley.LocalizedContentManager.LanguageCode;
using MiniMonoModHotfix = MonoMod.Utils.MiniMonoModHotfix;
@@ -67,8 +67,11 @@ namespace StardewModdingAPI.Framework
/****
** Low-level components
****/
+ /// <summary>A state which indicates whether SMAPI should exit immediately and any pending initialization should be cancelled.</summary>
+ private ExitState ExitState;
+
/// <summary>Whether the game should exit immediately and any pending initialization should be cancelled.</summary>
- private bool IsExiting;
+ private bool IsExiting => this.ExitState != ExitState.None;
/// <summary>Manages the SMAPI console window and log file.</summary>
private readonly LogManager LogManager;
@@ -297,22 +300,13 @@ namespace StardewModdingAPI.Framework
this.IsGameRunning = true;
StardewValley.Program.releaseBuild = true; // game's debug logic interferes with SMAPI opening the game window
this.Game.Run();
+ this.Dispose(isError: false);
}
catch (Exception ex)
{
this.LogManager.LogFatalLaunchError(ex);
this.LogManager.PressAnyKeyToExit();
- }
- finally
- {
- try
- {
- this.Dispose();
- }
- catch (Exception ex)
- {
- this.Monitor.Log($"The game ended, but SMAPI wasn't able to dispose correctly. Technical details: {ex}", LogLevel.Error);
- }
+ this.Dispose(isError: true);
}
}
@@ -328,6 +322,14 @@ namespace StardewModdingAPI.Framework
[SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "May be disposed before SMAPI is fully initialized.")]
public void Dispose()
{
+ this.Dispose(isError: true); // if we got here, SMAPI didn't detect the exit before it happened
+ }
+
+ /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
+ /// <param name="isError">Whether the process is exiting due to an error or crash.</param>
+ [SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "May be disposed before SMAPI is fully initialized.")]
+ public void Dispose(bool isError)
+ {
// skip if already disposed
if (this.IsDisposed)
return;
@@ -349,13 +351,29 @@ namespace StardewModdingAPI.Framework
// dispose core components
this.IsGameRunning = false;
- this.IsExiting = true;
+ if (this.ExitState == ExitState.None || isError)
+ this.ExitState = isError ? ExitState.Crash : ExitState.GameExit;
this.ContentCore?.Dispose();
this.Game?.Dispose();
this.LogManager.Dispose(); // dispose last to allow for any last-second log messages
- // end game (moved from Game1.OnExiting to let us clean up first)
- Process.GetCurrentProcess().Kill();
+ // clean up SDK
+ // This avoids Steam connection errors when it exits unexpectedly. The game avoids this
+ // by killing the entire process, but we can't set the error code if we do that.
+ try
+ {
+ FieldInfo? field = typeof(StardewValley.Program).GetField("_sdk", BindingFlags.NonPublic | BindingFlags.Static);
+ SDKHelper? sdk = field?.GetValue(null) as SDKHelper;
+ sdk?.Shutdown();
+ }
+ catch
+ {
+ // well, at least we tried
+ }
+
+ // end game with error code
+ // This helps the OS decide whether to keep the window open (e.g. Windows may keep it open on error).
+ Environment.Exit(this.ExitState == ExitState.Crash ? 1 : 0);
}
@@ -387,7 +405,14 @@ namespace StardewModdingAPI.Framework
{
string[] looseFiles = new DirectoryInfo(this.ModsPath).GetFiles().Select(p => p.Name).ToArray();
if (looseFiles.Any())
+ {
+ if (looseFiles.Any(name => name.Equals("manifest.json", StringComparison.OrdinalIgnoreCase) || name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)))
+ {
+ this.Monitor.Log($"Detected mod files directly inside the '{Path.GetFileName(this.ModsPath)}' folder. These will be ignored. Each mod must have its own subfolder instead.", LogLevel.Error);
+ }
+
this.Monitor.Log($" Ignored loose files: {string.Join(", ", looseFiles.OrderBy(p => p, StringComparer.OrdinalIgnoreCase))}");
+ }
}
// load manifests
@@ -1250,7 +1275,7 @@ namespace StardewModdingAPI.Framework
private void OnGameExiting()
{
this.Multiplayer.Disconnect(StardewValley.Multiplayer.DisconnectType.ClosedGame);
- this.Dispose();
+ this.Dispose(isError: false);
}
/// <summary>Raised when a mod network message is received.</summary>
@@ -1670,7 +1695,7 @@ namespace StardewModdingAPI.Framework
source: metadata,
nounPhrase: $"{nameof(IAssetEditor)}",
version: "3.14.0",
- severity: DeprecationLevel.Notice,
+ severity: DeprecationLevel.Info,
logStackTrace: false
);
@@ -1683,7 +1708,7 @@ namespace StardewModdingAPI.Framework
source: metadata,
nounPhrase: $"{nameof(IAssetLoader)}",
version: "3.14.0",
- severity: DeprecationLevel.Notice,
+ severity: DeprecationLevel.Info,
logStackTrace: false
);
@@ -1715,7 +1740,7 @@ namespace StardewModdingAPI.Framework
metadata,
$"using {name} without bundling it",
"3.14.7",
- DeprecationLevel.Notice,
+ DeprecationLevel.Info,
logStackTrace: false
);
}
@@ -2239,7 +2264,7 @@ namespace StardewModdingAPI.Framework
this.Monitor.LogFatal(message);
this.LogManager.WriteCrashLog();
- this.IsExiting = true;
+ this.ExitState = ExitState.Crash;
this.Game.Exit();
}
diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj
index c05512e9..36db0545 100644
--- a/src/SMAPI/SMAPI.csproj
+++ b/src/SMAPI/SMAPI.csproj
@@ -16,6 +16,7 @@
<!-- tiered compilation breaks Harmony -->
<TieredCompilation>false</TieredCompilation>
+ <ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<Import Project="..\..\build\common.targets" />
@@ -26,7 +27,7 @@
<PackageReference Include="MonoMod.Common" Version="22.3.5.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.1" />
- <PackageReference Include="Pintail" Version="2.2.0" />
+ <PackageReference Include="Pintail" Version="2.2.1" />
<PackageReference Include="Platonymous.TMXTile" Version="1.5.9" />
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs
index 54657ade..468df0bd 100644
--- a/src/SMAPI/Utilities/PerScreen.cs
+++ b/src/SMAPI/Utilities/PerScreen.cs
@@ -59,7 +59,7 @@ namespace StardewModdingAPI.Utilities
null,
$"calling the {nameof(PerScreen<T>)} constructor with null",
"3.14.0",
- DeprecationLevel.Notice
+ DeprecationLevel.Info
);
#else
throw new ArgumentNullException(nameof(createNewState));
diff --git a/src/SMAPI/app.manifest b/src/SMAPI/app.manifest
new file mode 100644
index 00000000..42faff59
--- /dev/null
+++ b/src/SMAPI/app.manifest
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+ <assemblyIdentity version="1.0.0.0" name="StardropEngine"/>
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+ <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- A list of the Windows versions that this application has been tested on and is
+ is designed to work with. Uncomment the appropriate elements and Windows will
+ automatically selected the most compatible environment. -->
+
+ <!-- Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
+
+ <!-- Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
+
+ <!-- Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+
+ <!-- Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
+
+ <!-- Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+ </application>
+ </compatibility>
+
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+ <windowsSettings>
+ <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
+ </windowsSettings>
+ </application>
+</assembly>
diff --git a/src/SMAPI/i18n/tr.json b/src/SMAPI/i18n/tr.json
index e97a48ba..ba0c33c9 100644
--- a/src/SMAPI/i18n/tr.json
+++ b/src/SMAPI/i18n/tr.json
@@ -2,5 +2,5 @@
// short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{day}} {{season}}",
- "generic.date-with-year": "{{day}} {{season}} года {{year}}"
+ "generic.date-with-year": "{{day}} {{season}} Yıl {{year}}"
}