summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/common.targets2
-rw-r--r--build/find-game-folder.targets22
-rw-r--r--docs/README.md38
-rw-r--r--docs/release-notes.md26
-rw-r--r--docs/technical/mod-package.md3
-rw-r--r--src/SMAPI.Installer/InteractiveInstaller.cs1
-rw-r--r--src/SMAPI.Installer/assets/unix-launcher.sh125
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/manifest.json4
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs30
-rw-r--r--src/SMAPI.Mods.ErrorHandler/i18n/uk.json4
-rw-r--r--src/SMAPI.Mods.ErrorHandler/manifest.json4
-rw-r--r--src/SMAPI.Mods.SaveBackup/manifest.json4
-rw-r--r--src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs11
-rw-r--r--src/SMAPI.Web/Controllers/LogParserController.cs33
-rw-r--r--src/SMAPI.Web/Framework/LogParsing/LogParser.cs35
-rw-r--r--src/SMAPI.Web/ViewModels/LogViewFormat.cs15
-rw-r--r--src/SMAPI.Web/Views/LogParser/Index.cshtml133
-rw-r--r--src/SMAPI.Web/wwwroot/Content/css/log-parser.css26
-rw-r--r--src/SMAPI.Web/wwwroot/Content/js/log-parser.js11
-rw-r--r--src/SMAPI.Web/wwwroot/SMAPI.metadata.json7
-rw-r--r--src/SMAPI.Web/wwwroot/schemas/manifest.json2
-rw-r--r--src/SMAPI.sln.DotSettings1
-rw-r--r--src/SMAPI/Constants.cs15
-rw-r--r--src/SMAPI/Framework/Content/TilesheetReference.cs15
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs4
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs39
-rw-r--r--src/SMAPI/Framework/Logging/InterceptingTextWriter.cs40
-rw-r--r--src/SMAPI/Framework/Logging/LogManager.cs37
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs24
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs54
-rw-r--r--src/SMAPI/Framework/Monitor.cs2
-rw-r--r--src/SMAPI/Metadata/InstructionMetadata.cs9
-rw-r--r--src/SMAPI/i18n/uk.json6
33 files changed, 494 insertions, 288 deletions
diff --git a/build/common.targets b/build/common.targets
index 1021c2a1..8eac9757 100644
--- a/build/common.targets
+++ b/build/common.targets
@@ -1,7 +1,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!--set general build properties -->
- <Version>3.13.2</Version>
+ <Version>3.13.3</Version>
<Product>SMAPI</Product>
<LangVersion>latest</LangVersion>
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
diff --git a/build/find-game-folder.targets b/build/find-game-folder.targets
index 3164b071..ba7cb26c 100644
--- a/build/find-game-folder.targets
+++ b/build/find-game-folder.targets
@@ -28,15 +28,31 @@
<_SteamLibraryPath>$([MSBuild]::GetRegistryValueFromView('HKEY_CURRENT_USER\SOFTWARE\Valve\Steam', 'SteamPath', null, RegistryView.Registry32))</_SteamLibraryPath>
<GamePath Condition="!Exists('$(GamePath)') AND '$(_SteamLibraryPath)' != ''">$(_SteamLibraryPath)\steamapps\common\Stardew Valley</GamePath>
- <!-- default paths -->
+ <!-- GOG paths -->
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GalaxyClient\Games\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GOG Galaxy\Games\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\GOG Games\Stardew Valley</GamePath>
- <GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\Steam\steamapps\common\Stardew Valley</GamePath>
-
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GOG Galaxy\Games\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GOG Games\Stardew Valley</GamePath>
+
+ <!-- Xbox app paths -->
+ <!--
+ The Xbox app saves the install path to the registry, but we can't use it here since it
+ saves the internal readonly path (like C:\Program Files\WindowsApps\Mutable\<package ID>)
+ instead of the mods-enabled path (like C:\Program Files\ModifiableWindowsApps\Stardew Valley).
+ Fortunately we can cheat a bit: players can customize the install drive, but they can't
+ change the install path on the drive.
+ -->
+ <GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\ModifiableWindowsApps\Stardew Valley</GamePath>
+ <GamePath Condition="!Exists('$(GamePath)')">D:\Program Files\ModifiableWindowsApps\Stardew Valley</GamePath>
+ <GamePath Condition="!Exists('$(GamePath)')">E:\Program Files\ModifiableWindowsApps\Stardew Valley</GamePath>
+ <GamePath Condition="!Exists('$(GamePath)')">F:\Program Files\ModifiableWindowsApps\Stardew Valley</GamePath>
+ <GamePath Condition="!Exists('$(GamePath)')">G:\Program Files\ModifiableWindowsApps\Stardew Valley</GamePath>
+ <GamePath Condition="!Exists('$(GamePath)')">H:\Program Files\ModifiableWindowsApps\Stardew Valley</GamePath>
+
+ <!-- Steam paths -->
+ <GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\Steam\steamapps\common\Stardew Valley</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley</GamePath>
</PropertyGroup>
</When>
diff --git a/docs/README.md b/docs/README.md
index ecfa6f2b..d3aaae64 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -56,22 +56,24 @@ SMAPI rarely shows text in-game, so it only has a few translations. Contribution
[Modding:Translations](https://stardewvalleywiki.com/Modding:Translations) on the wiki for help
contributing translations.
-locale | status
----------- | :----------------
-default | ✓ [fully translated](../src/SMAPI/i18n/default.json)
-Chinese | ✓ [fully translated](../src/SMAPI/i18n/zh.json)
-French | ✓ [fully translated](../src/SMAPI/i18n/fr.json)
-German | ✓ [fully translated](../src/SMAPI/i18n/de.json)
-Hungarian | ✓ [fully translated](../src/SMAPI/i18n/hu.json)
-Italian | ✓ [fully translated](../src/SMAPI/i18n/it.json)
-Japanese | ✓ [fully translated](../src/SMAPI/i18n/ja.json)
-Korean | ✓ [fully translated](../src/SMAPI/i18n/ko.json)
-Polish¹ | ✓ [fully translated](../src/SMAPI/i18n/pl.json)
-Portuguese | ✓ [fully translated](../src/SMAPI/i18n/pt.json)
-Russian | ✓ [fully translated](../src/SMAPI/i18n/ru.json)
-Spanish | ✓ [fully translated](../src/SMAPI/i18n/es.json)
-Thai¹ | ✓ [fully translated](../src/SMAPI/i18n/th.json)
-Turkish | ✓ [fully translated](../src/SMAPI/i18n/tr.json)
+locale | status
+----------- | :----------------
+default | ✓ [fully translated](../src/SMAPI/i18n/default.json)
+Chinese | ✓ [fully translated](../src/SMAPI/i18n/zh.json)
+French | ✓ [fully translated](../src/SMAPI/i18n/fr.json)
+German | ✓ [fully translated](../src/SMAPI/i18n/de.json)
+Hungarian | ✓ [fully translated](../src/SMAPI/i18n/hu.json)
+Italian | ✓ [fully translated](../src/SMAPI/i18n/it.json)
+Japanese | ✓ [fully translated](../src/SMAPI/i18n/ja.json)
+Korean | ✓ [fully translated](../src/SMAPI/i18n/ko.json)
+[Polish] | ✓ [fully translated](../src/SMAPI/i18n/pl.json)
+Portuguese | ✓ [fully translated](../src/SMAPI/i18n/pt.json)
+Russian | ✓ [fully translated](../src/SMAPI/i18n/ru.json)
+Spanish | ✓ [fully translated](../src/SMAPI/i18n/es.json)
+[Thai] | ✓ [fully translated](../src/SMAPI/i18n/th.json)
+Turkish | ✓ [fully translated](../src/SMAPI/i18n/tr.json)
+[Ukrainian] | ✓ [fully translated](../src/SMAPI/i18n/uk.json)
-¹ This is a custom language provided by a mod (see [Polish](https://www.nexusmods.com/stardewvalley/mods/3616)
-and [Thai](https://www.nexusmods.com/stardewvalley/mods/7052)).
+[Polish]: https://www.nexusmods.com/stardewvalley/mods/3616
+[Thai]: https://www.nexusmods.com/stardewvalley/mods/7052
+[Ukrainian]: https://www.nexusmods.com/stardewvalley/mods/8427
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 499fa322..957d5199 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,6 +1,32 @@
← [README](README.md)
# Release notes
+## 3.13.3
+Released 16 January 2021 for Stardew Valley 1.5.6 or later.
+
+* For players:
+ * **SMAPI now needs Stardew Valley 1.5.6 or later.**
+ * Added automatic fix for custom maps which are missing a required tilesheet.
+ * Added automatic save recovery when a custom farm type isn't available anymore.
+ * Added the game's new build number to the SMAPI console + log.
+ * The installer now detects Xbox app game folders.
+ * Reduced mod loading time a bit.
+ * Fixed macOS launch issue when using some terminals (thanks to bruce2409!).
+ * Fixed Linux/macOS terminal ignoring backspaces in Stardew Valley 1.5.5+.
+ * Fixed extra newlines in the SMAPI console.
+ * Fixed outdated instructions in Steam error message.
+ * Fixed uninstaller not removing `StardewModdingAPI.deps.json` file.
+ * Simplified [running without a terminal on Linux/macOS](https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting#SMAPI_doesn.27t_recognize_controller_.28Steam_only.29) when needed.
+ * Updated compatibility list.
+ * Improved translations. Thanks to ChulkyBow (added Ukrainian)!
+
+* For the web UI:
+ * Added log instructions for Xbox app on Windows.
+ * Added log download option.
+ * Redesigned log instruction UI.
+ * Fixed log parser not correctly handling multiple mods having the exact same name.
+ * Fixed JSON validator not recognizing manifest [update subkeys](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Update_checks#Update_subkeys).
+
## 3.13.2
Released 05 December 2021 for Stardew Valley 1.5.5 or later.
diff --git a/docs/technical/mod-package.md b/docs/technical/mod-package.md
index 41f808a5..5e408168 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
+* Added detection for Xbox app game folders.
+
## 4.0.0
Released 30 November 2021.
diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs
index 1257f12b..b3bba883 100644
--- a/src/SMAPI.Installer/InteractiveInstaller.cs
+++ b/src/SMAPI.Installer/InteractiveInstaller.cs
@@ -41,6 +41,7 @@ namespace StardewModdingApi.Installer
// current files
yield return GetInstallPath("StardewModdingAPI"); // Linux/macOS only
+ yield return GetInstallPath("StardewModdingAPI.deps.json");
yield return GetInstallPath("StardewModdingAPI.dll");
yield return GetInstallPath("StardewModdingAPI.exe");
yield return GetInstallPath("StardewModdingAPI.exe.config");
diff --git a/src/SMAPI.Installer/assets/unix-launcher.sh b/src/SMAPI.Installer/assets/unix-launcher.sh
index e8b9ae62..47937f95 100644
--- a/src/SMAPI.Installer/assets/unix-launcher.sh
+++ b/src/SMAPI.Installer/assets/unix-launcher.sh
@@ -6,6 +6,10 @@
# move to script's directory
cd "$(dirname "$0")" || exit $?
+# change to true to skip opening a terminal
+# This isn't recommended since you won't see errors, warnings, and update alerts.
+SKIP_TERMINAL=false
+
##########
## Open terminal if needed
@@ -16,7 +20,6 @@ cd "$(dirname "$0")" || exit $?
if [ "$(uname)" == "Darwin" ]; then
if [ ! -t 1 ]; then # https://stackoverflow.com/q/911168/262123
# sanity check to make sure we don't have an infinite loop of opening windows
- SKIP_TERMINAL=false
for argument in "$@"; do
if [ "$argument" == "--no-reopen-terminal" ]; then
SKIP_TERMINAL=true
@@ -28,7 +31,8 @@ if [ "$(uname)" == "Darwin" ]; then
# https://stackoverflow.com/a/29511052/262123
if [ "$SKIP_TERMINAL" == "false" ]; then
echo "Reopening in the Terminal app..."
- echo "\"$0\" $@ --no-reopen-terminal" > /tmp/open-smapi-terminal.sh
+ echo '#!/bin/sh' > /tmp/open-smapi-terminal.sh
+ echo "\"$0\" $@ --no-reopen-terminal" >> /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
@@ -63,64 +67,71 @@ else
LAUNCH_FILE="./StardewModdingAPI"
export LAUNCH_FILE
- # 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 -v "$terminal" 2>/dev/null; then
- export TERMINAL_NAME=$terminal
- break;
+ # run in terminal
+ if [ "$SKIP_TERMINAL" == "false" ]; then
+ # 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 -v "$terminal" 2>/dev/null; then
+ export TERMINAL_NAME=$terminal
+ break;
+ fi
+ done
+
+ # 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))")"
fi
- done
- # 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))")"
- fi
+ # run in selected terminal and account for quirks
+ export TERMINAL_PATH="$(command -v $TERMINAL_NAME)"
+ 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 $@"
+ ;;
+
+ xterm|konsole|alacritty)
+ # consumes all arguments after -e
+ 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 "$@"
+ ;;
+
+ gnome-terminal)
+ # consumes all arguments after --
+ exec $TERMINAL_NAME -- env TERM=xterm $LAUNCH_FILE "$@"
+ ;;
+
+ kitty)
+ # consumes all trailing arguments
+ exec $TERMINAL_NAME env TERM=xterm $LAUNCH_FILE "$@"
+ ;;
+
+ *)
+ # If we don't know the terminal, just try to run it in the current shell.
+ # If THAT fails, launch with no output.
+ env TERM=xterm $LAUNCH_FILE "$@"
+ if [ $? -eq 127 ]; then
+ exec $LAUNCH_FILE --no-terminal "$@"
+ fi
+ esac
+
+ ## terminal isn't executable; fallback to current shell or no terminal
+ else
+ echo "The '$TERMINAL_NAME' terminal isn't executable. SMAPI might be running in a sandbox or the system might be misconfigured? Falling back to current shell."
+ env TERM=xterm $LAUNCH_FILE "$@"
+ if [ $? -eq 127 ]; then
+ exec $LAUNCH_FILE --no-terminal "$@"
+ fi
+ fi
- # run in selected terminal and account for quirks
- export TERMINAL_PATH="$(command -v $TERMINAL_NAME)"
- 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 $@"
- ;;
-
- xterm|konsole|alacritty)
- # consumes all arguments after -e
- 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 "$@"
- ;;
-
- gnome-terminal)
- # consumes all arguments after --
- exec $TERMINAL_NAME -- env TERM=xterm $LAUNCH_FILE "$@"
- ;;
-
- kitty)
- # consumes all trailing arguments
- exec $TERMINAL_NAME env TERM=xterm $LAUNCH_FILE "$@"
- ;;
-
- *)
- # If we don't know the terminal, just try to run it in the current shell.
- # If THAT fails, launch with no output.
- env TERM=xterm $LAUNCH_FILE "$@"
- if [ $? -eq 127 ]; then
- exec $LAUNCH_FILE --no-terminal "$@"
- fi
- esac
-
- ## terminal isn't executable; fallback to current shell or no terminal
+ # explicitly run without terminal
else
- echo "The '$TERMINAL_NAME' terminal isn't executable. SMAPI might be running in a sandbox or the system might be misconfigured? Falling back to current shell."
- env TERM=xterm $LAUNCH_FILE "$@"
- if [ $? -eq 127 ]; then
- exec $LAUNCH_FILE --no-terminal "$@"
- fi
+ exec $LAUNCH_FILE --no-terminal "$@"
fi
fi
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json
index 216a4c32..97e1c243 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.13.2",
+ "Version": "3.13.3",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
- "MinimumApiVersion": "3.13.2"
+ "MinimumApiVersion": "3.13.3"
}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs
index 2a43cb10..0a7ed212 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs
@@ -4,11 +4,11 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using HarmonyLib;
using Microsoft.Xna.Framework.Content;
+using StardewModdingAPI.Internal;
using StardewModdingAPI.Internal.Patching;
using StardewValley;
using StardewValley.Buildings;
using StardewValley.Locations;
-using SObject = StardewValley.Object;
namespace StardewModdingAPI.Mods.ErrorHandler.Patches
{
@@ -47,6 +47,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
original: this.RequireMethod<SaveGame>(nameof(SaveGame.loadDataToLocations)),
prefix: this.GetHarmonyMethod(nameof(SaveGamePatcher.Before_LoadDataToLocations))
);
+
+ harmony.Patch(
+ original: this.RequireMethod<SaveGame>(nameof(SaveGame.LoadFarmType)),
+ finalizer: this.GetHarmonyMethod(nameof(SaveGamePatcher.Finalize_LoadFarmType))
+ );
}
@@ -58,14 +63,35 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
/// <returns>Returns whether to execute the original method.</returns>
private static bool Before_LoadDataToLocations(List<GameLocation> gamelocations)
{
+ // missing locations/NPCs
IDictionary<string, string> npcs = Game1.content.Load<Dictionary<string, string>>("Data\\NPCDispositions");
-
if (SaveGamePatcher.RemoveBrokenContent(gamelocations, npcs))
SaveGamePatcher.OnContentRemoved();
return true;
}
+ /// <summary>The method to call after <see cref="SaveGame.LoadFarmType"/> throws an exception.</summary>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ private static Exception Finalize_LoadFarmType(Exception __exception)
+ {
+ // missing custom farm type
+ if (__exception?.Message?.Contains("not a valid farm type") == true && !int.TryParse(SaveGame.loaded.whichFarm, out _))
+ {
+ SaveGamePatcher.Monitor.Log(__exception.GetLogSummary(), LogLevel.Error);
+ SaveGamePatcher.Monitor.Log($"Removed invalid custom farm type '{SaveGame.loaded.whichFarm}' to avoid a crash when loading save '{Constants.SaveFolderName}'. (Did you remove a custom farm type mod?)", LogLevel.Warn);
+
+ SaveGame.loaded.whichFarm = Farm.default_layout.ToString();
+ SaveGame.LoadFarmType();
+ SaveGamePatcher.OnContentRemoved();
+
+ __exception = null;
+ }
+
+ return __exception;
+ }
+
/// <summary>Remove content which no longer exists in the game data.</summary>
/// <param name="locations">The current game locations.</param>
/// <param name="npcs">The NPC data.</param>
diff --git a/src/SMAPI.Mods.ErrorHandler/i18n/uk.json b/src/SMAPI.Mods.ErrorHandler/i18n/uk.json
new file mode 100644
index 00000000..a58102ab
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/i18n/uk.json
@@ -0,0 +1,4 @@
+{
+ // warning messages
+ "warn.invalid-content-removed": "Недійсний вміст видалено, щоб запобігти аварійному завершенню роботи (Додаткову інформацію див. на консолі SMAPI)."
+}
diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json
index beb52020..c4246721 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.13.2",
+ "Version": "3.13.3",
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
"UniqueID": "SMAPI.ErrorHandler",
"EntryDll": "ErrorHandler.dll",
- "MinimumApiVersion": "3.13.2"
+ "MinimumApiVersion": "3.13.3"
}
diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json
index 2bd20a63..b1b946c8 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.13.2",
+ "Version": "3.13.3",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
- "MinimumApiVersion": "3.13.2"
+ "MinimumApiVersion": "3.13.3"
}
diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs
index 37e4f263..8d4198de 100644
--- a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs
+++ b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs
@@ -157,7 +157,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
yield return Path.Combine(steamPath.Replace('/', '\\'), @"steamapps\common\Stardew Valley");
#endif
- // default paths
+ // default GOG/Steam paths
foreach (string programFiles in new[] { @"C:\Program Files", @"C:\Program Files (x86)" })
{
yield return $@"{programFiles}\GalaxyClient\Games\Stardew Valley";
@@ -165,6 +165,15 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
yield return $@"{programFiles}\GOG Games\Stardew Valley";
yield return $@"{programFiles}\Steam\steamapps\common\Stardew Valley";
}
+
+ // default Xbox app paths
+ // The Xbox app saves the install path to the registry, but we can't use it
+ // here since it saves the internal readonly path (like C:\Program Files\WindowsApps\Mutable\<package ID>)
+ // instead of the mods-enabled path(like C:\Program Files\ModifiableWindowsApps\Stardew Valley).
+ // Fortunately we can cheat a bit: players can customize the install drive, but they can't
+ // change the install path on the drive.
+ for (char driveLetter = 'C'; driveLetter <= 'H'; driveLetter++)
+ yield return $@"{driveLetter}:\Program Files\ModifiableWindowsApps\Stardew Valley";
}
break;
diff --git a/src/SMAPI.Web/Controllers/LogParserController.cs b/src/SMAPI.Web/Controllers/LogParserController.cs
index 39de4b5d..db53d942 100644
--- a/src/SMAPI.Web/Controllers/LogParserController.cs
+++ b/src/SMAPI.Web/Controllers/LogParserController.cs
@@ -1,5 +1,6 @@
using System;
using System.Linq;
+using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using StardewModdingAPI.Toolkit.Utilities;
@@ -39,24 +40,42 @@ namespace StardewModdingAPI.Web.Controllers
***/
/// <summary>Render the log parser UI.</summary>
/// <param name="id">The stored file ID.</param>
- /// <param name="raw">Whether to display the raw unparsed log.</param>
+ /// <param name="format">How to render the log view.</param>
/// <param name="renew">Whether to reset the log expiry.</param>
[HttpGet]
[Route("log")]
[Route("log/{id}")]
- public async Task<ViewResult> Index(string id = null, bool raw = false, bool renew = false)
+ public async Task<ActionResult> Index(string id = null, LogViewFormat format = LogViewFormat.Default, bool renew = false)
{
// fresh page
if (string.IsNullOrWhiteSpace(id))
return this.View("Index", this.GetModel(id));
- // log page
+ // fetch log
StoredFileInfo file = await this.Storage.GetAsync(id, renew);
- ParsedLog log = file.Success
- ? new LogParser().Parse(file.Content)
- : new ParsedLog { IsValid = false, Error = file.Error };
- return this.View("Index", this.GetModel(id, uploadWarning: file.Warning, expiry: file.Expiry).SetResult(log, raw));
+ // render view
+ switch (format)
+ {
+ case LogViewFormat.Default:
+ case LogViewFormat.RawView:
+ {
+ ParsedLog log = file.Success
+ ? new LogParser().Parse(file.Content)
+ : new ParsedLog { IsValid = false, Error = file.Error };
+
+ return this.View("Index", this.GetModel(id, uploadWarning: file.Warning, expiry: file.Expiry).SetResult(log, showRaw: format == LogViewFormat.RawView));
+ }
+
+ case LogViewFormat.RawDownload:
+ {
+ string content = file.Error ?? file.Content;
+ return this.File(Encoding.UTF8.GetBytes(content), "plain/text", $"SMAPI log ({id}).txt");
+ }
+
+ default:
+ throw new InvalidOperationException($"Unknown log view format '{format}'.");
+ }
}
/***
diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
index 84013ccc..887d0105 100644
--- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
+++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs
@@ -79,7 +79,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
// parse log messages
LogModInfo smapiMod = new LogModInfo { Name = "SMAPI", Author = "Pathoschild", Description = "", Loaded = true };
LogModInfo gameMod = new LogModInfo { Name = "game", Author = "", Description = "", Loaded = true };
- IDictionary<string, LogModInfo> mods = new Dictionary<string, LogModInfo>();
+ IDictionary<string, List<LogModInfo>> mods = new Dictionary<string, List<LogModInfo>>();
bool inModList = false;
bool inContentPackList = false;
bool inModUpdateList = false;
@@ -99,8 +99,11 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
break;
default:
- if (mods.ContainsKey(message.Mod))
- mods[message.Mod].Errors++;
+ if (mods.TryGetValue(message.Mod, out var entries))
+ {
+ foreach (var entry in entries)
+ entry.Errors++;
+ }
break;
}
}
@@ -127,7 +130,10 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
string version = match.Groups["version"].Value;
string author = match.Groups["author"].Value;
string description = match.Groups["description"].Value;
- mods[name] = new LogModInfo { Name = name, Author = author, Version = version, Description = description, Loaded = true };
+
+ 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 });
message.Section = LogSection.ModsList;
}
@@ -147,7 +153,10 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
string author = match.Groups["author"].Value;
string description = match.Groups["description"].Value;
string forMod = match.Groups["for"].Value;
- mods[name] = new LogModInfo { Name = name, Author = author, Version = version, Description = description, ContentPackFor = forMod, Loaded = true };
+
+ 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 });
message.Section = LogSection.ContentPackList;
}
@@ -165,14 +174,14 @@ namespace StardewModdingAPI.Web.Framework.LogParsing
string name = match.Groups["name"].Value;
string version = match.Groups["version"].Value;
string link = match.Groups["link"].Value;
- if (mods.ContainsKey(name))
- {
- mods[name].UpdateLink = link;
- mods[name].UpdateVersion = version;
- }
- else