diff options
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 |
