move error-handling Harmony patches into a new Error Handler bundled mod

This commit is contained in:
Jesse Plamondon-Willard 2021-01-15 18:48:29 -05:00
parent 5953fc3bd0
commit 8a475b3579
No known key found for this signature in database
GPG Key ID: CF8B1456B3E29F49
39 changed files with 223 additions and 74 deletions

View File

@ -37,16 +37,24 @@
<Copy SourceFiles="$(TargetDir)\TMXTile.dll" DestinationFolder="$(GamePath)\smapi-internal" /> <Copy SourceFiles="$(TargetDir)\TMXTile.dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\smapi-internal\i18n" /> <Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\smapi-internal\i18n" />
</Target> </Target>
<Target Name="CopyDefaultMods" Condition="'$(MSBuildProjectName)' == 'SMAPI.Mods.ConsoleCommands' OR '$(MSBuildProjectName)' == 'SMAPI.Mods.SaveBackup'">
<Target Name="CopyDefaultMods" Condition="'$(MSBuildProjectName)' == 'SMAPI.Mods.ConsoleCommands' OR '$(MSBuildProjectName)' == 'SMAPI.Mods.ErrorHandler' OR '$(MSBuildProjectName)' == 'SMAPI.Mods.SaveBackup'">
<ItemGroup>
<TranslationFiles Include="$(TargetDir)\i18n\*.json" />
</ItemGroup>
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" /> <Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" Condition="Exists('$(TargetDir)\$(TargetName).pdb')" /> <Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" Condition="Exists('$(TargetDir)\$(TargetName).pdb')" />
<Copy SourceFiles="$(TargetDir)\manifest.json" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" /> <Copy SourceFiles="$(TargetDir)\manifest.json" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)" />
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)\i18n" />
</Target> </Target>
<Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent"> <Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" /> <Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" /> <Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" /> <Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" />
</Target> </Target>
<Target Name="CopyToolkitCoreInterfaces" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit.CoreInterfaces' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent"> <Target Name="CopyToolkitCoreInterfaces" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit.CoreInterfaces' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" /> <Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" /> <Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" />

View File

@ -16,6 +16,7 @@
<SmapiBin>$(BuildRootPath)\SMAPI\bin\$(Configuration)</SmapiBin> <SmapiBin>$(BuildRootPath)\SMAPI\bin\$(Configuration)</SmapiBin>
<ToolkitBin>$(BuildRootPath)\SMAPI.Toolkit\bin\$(Configuration)\net4.5</ToolkitBin> <ToolkitBin>$(BuildRootPath)\SMAPI.Toolkit\bin\$(Configuration)\net4.5</ToolkitBin>
<ConsoleCommandsBin>$(BuildRootPath)\SMAPI.Mods.ConsoleCommands\bin\$(Configuration)</ConsoleCommandsBin> <ConsoleCommandsBin>$(BuildRootPath)\SMAPI.Mods.ConsoleCommands\bin\$(Configuration)</ConsoleCommandsBin>
<ErrorHandlerBin>$(BuildRootPath)\SMAPI.Mods.ErrorHandler\bin\$(Configuration)</ErrorHandlerBin>
<SaveBackupBin>$(BuildRootPath)\SMAPI.Mods.SaveBackup\bin\$(Configuration)</SaveBackupBin> <SaveBackupBin>$(BuildRootPath)\SMAPI.Mods.SaveBackup\bin\$(Configuration)</SaveBackupBin>
<PackagePath>$(OutRootPath)\SMAPI installer</PackagePath> <PackagePath>$(OutRootPath)\SMAPI installer</PackagePath>
@ -23,6 +24,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<TranslationFiles Include="$(SmapiBin)\i18n\*.json" /> <TranslationFiles Include="$(SmapiBin)\i18n\*.json" />
<ErrorHandlerTranslationFiles Include="$(ErrorHandlerBin)\i18n\*.json" />
</ItemGroup> </ItemGroup>
<!-- reset package directory --> <!-- reset package directory -->
@ -64,6 +66,10 @@
<Copy SourceFiles="$(ConsoleCommandsBin)\ConsoleCommands.dll" DestinationFolder="$(PackagePath)\bundle\Mods\ConsoleCommands" /> <Copy SourceFiles="$(ConsoleCommandsBin)\ConsoleCommands.dll" DestinationFolder="$(PackagePath)\bundle\Mods\ConsoleCommands" />
<Copy SourceFiles="$(ConsoleCommandsBin)\ConsoleCommands.pdb" DestinationFolder="$(PackagePath)\bundle\Mods\ConsoleCommands" /> <Copy SourceFiles="$(ConsoleCommandsBin)\ConsoleCommands.pdb" DestinationFolder="$(PackagePath)\bundle\Mods\ConsoleCommands" />
<Copy SourceFiles="$(ConsoleCommandsBin)\manifest.json" DestinationFolder="$(PackagePath)\bundle\Mods\ConsoleCommands" /> <Copy SourceFiles="$(ConsoleCommandsBin)\manifest.json" DestinationFolder="$(PackagePath)\bundle\Mods\ConsoleCommands" />
<Copy SourceFiles="$(ErrorHandlerBin)\ErrorHandler.dll" DestinationFolder="$(PackagePath)\bundle\Mods\ErrorHandler" />
<Copy SourceFiles="$(ErrorHandlerBin)\ErrorHandler.pdb" DestinationFolder="$(PackagePath)\bundle\Mods\ErrorHandler" />
<Copy SourceFiles="$(ErrorHandlerBin)\manifest.json" DestinationFolder="$(PackagePath)\bundle\Mods\ErrorHandler" />
<Copy SourceFiles="@(ErrorHandlerTranslationFiles)" DestinationFolder="$(PackagePath)\bundle\Mods\ErrorHandler\i18n" />
<Copy SourceFiles="$(SaveBackupBin)\SaveBackup.dll" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" /> <Copy SourceFiles="$(SaveBackupBin)\SaveBackup.dll" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" />
<Copy SourceFiles="$(SaveBackupBin)\SaveBackup.pdb" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" /> <Copy SourceFiles="$(SaveBackupBin)\SaveBackup.pdb" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" />
<Copy SourceFiles="$(SaveBackupBin)\manifest.json" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" /> <Copy SourceFiles="$(SaveBackupBin)\manifest.json" DestinationFolder="$(PackagePath)\bundle\Mods\SaveBackup" />

View File

@ -7,6 +7,10 @@
* Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).
--> -->
## Upcoming release
* For the Error Handler mod:
* Added in SMAPI 3.9. This has vanilla error-handling that was previously added by SMAPI directly. That simplifies the core SMAPI logic, and lets players or modders disable it if needed.
## 3.8.4 ## 3.8.4
Released 15 January 2021 for Stardew Valley 1.5.3 or later. Released 15 January 2021 for Stardew Valley 1.5.3 or later.

View File

@ -0,0 +1,73 @@
using System.Reflection;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Logging;
using StardewModdingAPI.Framework.Patching;
using StardewModdingAPI.Mods.ErrorHandler.Patches;
using StardewValley;
namespace StardewModdingAPI.Mods.ErrorHandler
{
/// <summary>The main entry point for the mod.</summary>
public class ModEntry : Mod
{
/*********
** Private methods
*********/
/// <summary>Whether custom content was removed from the save data to avoid a crash.</summary>
private bool IsSaveContentRemoved;
/*********
** Public methods
*********/
/// <summary>The mod entry point, called after the mod is first loaded.</summary>
/// <param name="helper">Provides simplified APIs for writing mods.</param>
public override void Entry(IModHelper helper)
{
// get SMAPI core types
SCore core = SCore.Instance;
LogManager logManager = core.GetType().GetField("LogManager", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(core) as LogManager;
if (logManager == null)
{
this.Monitor.Log($"Can't access SMAPI's internal log manager. Error-handling patches won't be applied.", LogLevel.Error);
return;
}
// apply patches
new GamePatcher(this.Monitor).Apply(
new EventErrorPatch(logManager.MonitorForGame),
new DialogueErrorPatch(logManager.MonitorForGame, this.Helper.Reflection),
new ObjectErrorPatch(),
new LoadErrorPatch(this.Monitor, this.OnSaveContentRemoved),
new ScheduleErrorPatch(logManager.MonitorForGame)
);
// hook events
this.Helper.Events.GameLoop.SaveLoaded += this.OnSaveLoaded;
}
/*********
** Private methods
*********/
/// <summary>Raised after custom content is removed from the save data to avoid a crash.</summary>
internal void OnSaveContentRemoved()
{
this.IsSaveContentRemoved = true;
}
/// <summary>The method invoked when a save is loaded.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
public void OnSaveLoaded(object sender, SaveLoadedEventArgs e)
{
// show in-game warning for removed save content
if (this.IsSaveContentRemoved)
{
this.IsSaveContentRemoved = false;
Game1.addHUDMessage(new HUDMessage(this.Helper.Translation.Get("warn.invalid-content-removed"), HUDMessage.error_type));
}
}
}
}

View File

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Patching;
using StardewModdingAPI.Framework.Reflection;
using StardewValley; using StardewValley;
#if HARMONY_2 #if HARMONY_2
using HarmonyLib; using HarmonyLib;
@ -12,7 +11,7 @@ using System.Reflection;
using Harmony; using Harmony;
#endif #endif
namespace StardewModdingAPI.Patches namespace StardewModdingAPI.Mods.ErrorHandler.Patches
{ {
/// <summary>A Harmony patch for the <see cref="Dialogue"/> constructor which intercepts invalid dialogue lines and logs an error instead of crashing.</summary> /// <summary>A Harmony patch for the <see cref="Dialogue"/> constructor which intercepts invalid dialogue lines and logs an error instead of crashing.</summary>
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks> /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
@ -27,7 +26,7 @@ namespace StardewModdingAPI.Patches
private static IMonitor MonitorForGame; private static IMonitor MonitorForGame;
/// <summary>Simplifies access to private code.</summary> /// <summary>Simplifies access to private code.</summary>
private static Reflector Reflection; private static IReflectionHelper Reflection;
/********* /*********
@ -43,7 +42,7 @@ namespace StardewModdingAPI.Patches
/// <summary>Construct an instance.</summary> /// <summary>Construct an instance.</summary>
/// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param> /// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
/// <param name="reflector">Simplifies access to private code.</param> /// <param name="reflector">Simplifies access to private code.</param>
public DialogueErrorPatch(IMonitor monitorForGame, Reflector reflector) public DialogueErrorPatch(IMonitor monitorForGame, IReflectionHelper reflector)
{ {
DialogueErrorPatch.MonitorForGame = monitorForGame; DialogueErrorPatch.MonitorForGame = monitorForGame;
DialogueErrorPatch.Reflection = reflector; DialogueErrorPatch.Reflection = reflector;
@ -167,7 +166,7 @@ namespace StardewModdingAPI.Patches
/// <returns>Returns whether to execute the original method.</returns> /// <returns>Returns whether to execute the original method.</returns>
private static bool Before_NPC_CurrentDialogue(NPC __instance, ref Stack<Dialogue> __result, MethodInfo __originalMethod) private static bool Before_NPC_CurrentDialogue(NPC __instance, ref Stack<Dialogue> __result, MethodInfo __originalMethod)
{ {
const string key = nameof(Before_NPC_CurrentDialogue); const string key = nameof(DialogueErrorPatch.Before_NPC_CurrentDialogue);
if (!PatchHelper.StartIntercept(key)) if (!PatchHelper.StartIntercept(key))
return true; return true;

View File

@ -9,7 +9,7 @@ using Harmony;
using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Patching;
using StardewValley; using StardewValley;
namespace StardewModdingAPI.Patches namespace StardewModdingAPI.Mods.ErrorHandler.Patches
{ {
/// <summary>A Harmony patch for <see cref="GameLocation.checkEventPrecondition"/> which intercepts invalid preconditions and logs an error instead of crashing.</summary> /// <summary>A Harmony patch for <see cref="GameLocation.checkEventPrecondition"/> which intercepts invalid preconditions and logs an error instead of crashing.</summary>
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks> /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
@ -89,7 +89,7 @@ namespace StardewModdingAPI.Patches
/// <returns>Returns whether to execute the original method.</returns> /// <returns>Returns whether to execute the original method.</returns>
private static bool Before_GameLocation_CheckEventPrecondition(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod) private static bool Before_GameLocation_CheckEventPrecondition(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod)
{ {
const string key = nameof(Before_GameLocation_CheckEventPrecondition); const string key = nameof(EventErrorPatch.Before_GameLocation_CheckEventPrecondition);
if (!PatchHelper.StartIntercept(key)) if (!PatchHelper.StartIntercept(key))
return true; return true;

View File

@ -13,7 +13,7 @@ using StardewValley;
using StardewValley.Buildings; using StardewValley.Buildings;
using StardewValley.Locations; using StardewValley.Locations;
namespace StardewModdingAPI.Patches namespace StardewModdingAPI.Mods.ErrorHandler.Patches
{ {
/// <summary>A Harmony patch for <see cref="SaveGame"/> which prevents some errors due to broken save data.</summary> /// <summary>A Harmony patch for <see cref="SaveGame"/> which prevents some errors due to broken save data.</summary>
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks> /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>

View File

@ -12,7 +12,7 @@ using System.Reflection;
using Harmony; using Harmony;
#endif #endif
namespace StardewModdingAPI.Patches namespace StardewModdingAPI.Mods.ErrorHandler.Patches
{ {
/// <summary>A Harmony patch for <see cref="SObject.getDescription"/> which intercepts crashes due to the item no longer existing.</summary> /// <summary>A Harmony patch for <see cref="SObject.getDescription"/> which intercepts crashes due to the item no longer existing.</summary>
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks> /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
@ -103,7 +103,7 @@ namespace StardewModdingAPI.Patches
/// <returns>Returns whether to execute the original method.</returns> /// <returns>Returns whether to execute the original method.</returns>
private static bool Before_Object_loadDisplayName(SObject __instance, ref string __result, MethodInfo __originalMethod) private static bool Before_Object_loadDisplayName(SObject __instance, ref string __result, MethodInfo __originalMethod)
{ {
const string key = nameof(Before_Object_loadDisplayName); const string key = nameof(ObjectErrorPatch.Before_Object_loadDisplayName);
if (!PatchHelper.StartIntercept(key)) if (!PatchHelper.StartIntercept(key))
return true; return true;

View File

@ -11,7 +11,7 @@ using System.Reflection;
using Harmony; using Harmony;
#endif #endif
namespace StardewModdingAPI.Patches namespace StardewModdingAPI.Mods.ErrorHandler.Patches
{ {
/// <summary>A Harmony patch for <see cref="NPC.parseMasterSchedule"/> which intercepts crashes due to invalid schedule data.</summary> /// <summary>A Harmony patch for <see cref="NPC.parseMasterSchedule"/> which intercepts crashes due to invalid schedule data.</summary>
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks> /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
@ -90,7 +90,7 @@ namespace StardewModdingAPI.Patches
/// <returns>Returns whether to execute the original method.</returns> /// <returns>Returns whether to execute the original method.</returns>
private static bool Before_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary<int, SchedulePathDescription> __result, MethodInfo __originalMethod) private static bool Before_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary<int, SchedulePathDescription> __result, MethodInfo __originalMethod)
{ {
const string key = nameof(Before_NPC_parseMasterSchedule); const string key = nameof(ScheduleErrorPatch.Before_NPC_parseMasterSchedule);
if (!PatchHelper.StartIntercept(key)) if (!PatchHelper.StartIntercept(key))
return true; return true;

View File

@ -0,0 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>ErrorHandler</AssemblyName>
<RootNamespace>StardewModdingAPI.Mods.ErrorHandler</RootNamespace>
<TargetFramework>net45</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SMAPI\SMAPI.csproj" Private="False" />
<Reference Include="..\..\build\0Harmony.dll" Private="False" />
</ItemGroup>
<ItemGroup>
<Reference Include="$(GameExecutableName)" HintPath="$(GamePath)\$(GameExecutableName).exe" Private="False" />
</ItemGroup>
<Choose>
<!-- Windows -->
<When Condition="$(OS) == 'Windows_NT'">
<ItemGroup>
<Reference Include="Netcode" HintPath="$(GamePath)\Netcode.dll" Private="False" />
<Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
<Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
<Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
<Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" Private="False" />
</ItemGroup>
</When>
<!-- Linux/Mac -->
<Otherwise>
<ItemGroup>
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
</ItemGroup>
</Otherwise>
</Choose>
<ItemGroup>
<None Update="i18n\*.json" CopyToOutputDirectory="PreserveNewest" />
<None Update="manifest.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
<Import Project="..\..\build\common.targets" />
</Project>

View File

@ -0,0 +1,4 @@
{
// warning messages
"warn.invalid-content-removed": "Ungültiger Inhalt wurde entfernt, um einen Absturz zu verhindern (siehe SMAPI Konsole für weitere Informationen)."
}

View File

@ -0,0 +1,4 @@
{
// warning messages
"warn.invalid-content-removed": "Invalid content was removed to prevent a crash (see the SMAPI console for info)."
}

View File

@ -0,0 +1,4 @@
{
// warning messages
"warn.invalid-content-removed": "Se ha quitado contenido inválido para evitar un cierre forzoso (revisa la consola de SMAPI para más información)."
}

View File

@ -0,0 +1,4 @@
{
// warning messages
"warn.invalid-content-removed": "Le contenu non valide a été supprimé afin d'éviter un plantage (voir la console de SMAPI pour plus d'informations)."
}

View File

@ -0,0 +1,4 @@
{
// warning messages
"warn.invalid-content-removed": "Érvénytelen elemek kerültek eltávolításra, hogy a játék ne omoljon össze (további információk a SMAPI konzolon)."
}

View File

@ -0,0 +1,4 @@
{
// warning messages
"warn.invalid-content-removed": "Contenuto non valido rimosso per prevenire un crash (Guarda la console di SMAPI per maggiori informazioni)."
}

View File

@ -0,0 +1,4 @@
{
// warning messages
"warn.invalid-content-removed": "クラッシュを防ぐために無効なコンテンツを取り除きました 詳細はSMAPIコンソールを参照"
}

View File

@ -0,0 +1,4 @@
{
// warning messages
"warn.invalid-content-removed": "충돌을 방지하기 위해 잘못된 컨텐츠가 제거되었습니다 (자세한 내용은 SMAPI 콘솔 참조)."
}

View File

@ -0,0 +1,4 @@
{
// warning messages
"warn.invalid-content-removed": "Conteúdo inválido foi removido para prevenir uma falha (veja o console do SMAPI para mais informações)."
}

View File

@ -0,0 +1,4 @@
{
// warning messages
"warn.invalid-content-removed": "Недопустимое содержимое было удалено, чтобы предотвратить сбой (см. информацию в консоли SMAPI)"
}

View File

@ -0,0 +1,4 @@
{
// warning messages
"warn.invalid-content-removed": "Yanlış paketlenmiş bir içerik, oyunun çökmemesi için yüklenmedi (SMAPI konsol penceresinde detaylı bilgi mevcut)."
}

View File

@ -0,0 +1,4 @@
{
// warning messages
"warn.invalid-content-removed": "非法内容已移除以防游戏闪退查看SMAPI控制台获得更多信息"
}

View File

@ -0,0 +1,9 @@
{
"Name": "Error Handler",
"Author": "SMAPI",
"Version": "3.8.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.8.3"
}

View File

@ -71,6 +71,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.ModBuildConfig.Analyz
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Mods.ConsoleCommands", "SMAPI.Mods.ConsoleCommands\SMAPI.Mods.ConsoleCommands.csproj", "{0634EA4C-3B8F-42DB-AEA6-CA9E4EF6E92F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Mods.ConsoleCommands", "SMAPI.Mods.ConsoleCommands\SMAPI.Mods.ConsoleCommands.csproj", "{0634EA4C-3B8F-42DB-AEA6-CA9E4EF6E92F}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Mods.ErrorHandler", "SMAPI.Mods.ErrorHandler\SMAPI.Mods.ErrorHandler.csproj", "{491E775B-EAD0-44D4-B6CA-F1FC3E316D33}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Mods.SaveBackup", "SMAPI.Mods.SaveBackup\SMAPI.Mods.SaveBackup.csproj", "{CD53AD6F-97F4-4872-A212-50C2A0FD3601}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Mods.SaveBackup", "SMAPI.Mods.SaveBackup\SMAPI.Mods.SaveBackup.csproj", "{CD53AD6F-97F4-4872-A212-50C2A0FD3601}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Toolkit", "SMAPI.Toolkit\SMAPI.Toolkit.csproj", "{08184F74-60AD-4EEE-A78C-F4A35ADE6246}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Toolkit", "SMAPI.Toolkit\SMAPI.Toolkit.csproj", "{08184F74-60AD-4EEE-A78C-F4A35ADE6246}"
@ -83,6 +85,7 @@ Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution GlobalSection(SharedMSBuildProjectFiles) = preSolution
SMAPI.Internal\SMAPI.Internal.projitems*{0634ea4c-3b8f-42db-aea6-ca9e4ef6e92f}*SharedItemsImports = 5 SMAPI.Internal\SMAPI.Internal.projitems*{0634ea4c-3b8f-42db-aea6-ca9e4ef6e92f}*SharedItemsImports = 5
SMAPI.Internal\SMAPI.Internal.projitems*{0a9bb24f-15ff-4c26-b1a2-81f7ae316518}*SharedItemsImports = 5 SMAPI.Internal\SMAPI.Internal.projitems*{0a9bb24f-15ff-4c26-b1a2-81f7ae316518}*SharedItemsImports = 5
SMAPI.Internal\SMAPI.Internal.projitems*{491e775b-ead0-44d4-b6ca-f1fc3e316d33}*SharedItemsImports = 5
SMAPI.Internal\SMAPI.Internal.projitems*{80efd92f-728f-41e0-8a5b-9f6f49a91899}*SharedItemsImports = 5 SMAPI.Internal\SMAPI.Internal.projitems*{80efd92f-728f-41e0-8a5b-9f6f49a91899}*SharedItemsImports = 5
SMAPI.Internal\SMAPI.Internal.projitems*{85208f8d-6fd1-4531-be05-7142490f59fe}*SharedItemsImports = 13 SMAPI.Internal\SMAPI.Internal.projitems*{85208f8d-6fd1-4531-be05-7142490f59fe}*SharedItemsImports = 13
SMAPI.Internal\SMAPI.Internal.projitems*{cd53ad6f-97f4-4872-a212-50c2a0fd3601}*SharedItemsImports = 5 SMAPI.Internal\SMAPI.Internal.projitems*{cd53ad6f-97f4-4872-a212-50c2a0fd3601}*SharedItemsImports = 5
@ -121,6 +124,10 @@ Global
{0634EA4C-3B8F-42DB-AEA6-CA9E4EF6E92F}.Debug|Any CPU.Build.0 = Debug|Any CPU {0634EA4C-3B8F-42DB-AEA6-CA9E4EF6E92F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0634EA4C-3B8F-42DB-AEA6-CA9E4EF6E92F}.Release|Any CPU.ActiveCfg = Release|Any CPU {0634EA4C-3B8F-42DB-AEA6-CA9E4EF6E92F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0634EA4C-3B8F-42DB-AEA6-CA9E4EF6E92F}.Release|Any CPU.Build.0 = Release|Any CPU {0634EA4C-3B8F-42DB-AEA6-CA9E4EF6E92F}.Release|Any CPU.Build.0 = Release|Any CPU
{491E775B-EAD0-44D4-B6CA-F1FC3E316D33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{491E775B-EAD0-44D4-B6CA-F1FC3E316D33}.Debug|Any CPU.Build.0 = Debug|Any CPU
{491E775B-EAD0-44D4-B6CA-F1FC3E316D33}.Release|Any CPU.ActiveCfg = Release|Any CPU
{491E775B-EAD0-44D4-B6CA-F1FC3E316D33}.Release|Any CPU.Build.0 = Release|Any CPU
{CD53AD6F-97F4-4872-A212-50C2A0FD3601}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CD53AD6F-97F4-4872-A212-50C2A0FD3601}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD53AD6F-97F4-4872-A212-50C2A0FD3601}.Debug|Any CPU.Build.0 = Debug|Any CPU {CD53AD6F-97F4-4872-A212-50C2A0FD3601}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD53AD6F-97F4-4872-A212-50C2A0FD3601}.Release|Any CPU.ActiveCfg = Release|Any CPU {CD53AD6F-97F4-4872-A212-50C2A0FD3601}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -151,6 +158,7 @@ Global
{680B2641-81EA-467C-86A5-0E81CDC57ED0} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11} {680B2641-81EA-467C-86A5-0E81CDC57ED0} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}
{AA95884B-7097-476E-92C8-D0500DE9D6D1} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11} {AA95884B-7097-476E-92C8-D0500DE9D6D1} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}
{0634EA4C-3B8F-42DB-AEA6-CA9E4EF6E92F} = {AE9A4D46-E910-4293-8BC4-673F85FFF6A5} {0634EA4C-3B8F-42DB-AEA6-CA9E4EF6E92F} = {AE9A4D46-E910-4293-8BC4-673F85FFF6A5}
{491E775B-EAD0-44D4-B6CA-F1FC3E316D33} = {AE9A4D46-E910-4293-8BC4-673F85FFF6A5}
{CD53AD6F-97F4-4872-A212-50C2A0FD3601} = {AE9A4D46-E910-4293-8BC4-673F85FFF6A5} {CD53AD6F-97F4-4872-A212-50C2A0FD3601} = {AE9A4D46-E910-4293-8BC4-673F85FFF6A5}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution

View File

@ -124,9 +124,6 @@ namespace StardewModdingAPI.Framework
/// <summary>The maximum number of consecutive attempts SMAPI should make to recover from an update error.</summary> /// <summary>The maximum number of consecutive attempts SMAPI should make to recover from an update error.</summary>
private readonly Countdown UpdateCrashTimer = new Countdown(60); // 60 ticks = roughly one second private readonly Countdown UpdateCrashTimer = new Countdown(60); // 60 ticks = roughly one second
/// <summary>Whether custom content was removed from the save data to avoid a crash.</summary>
private bool IsSaveContentRemoved;
/// <summary>Asset interceptors added or removed since the last tick.</summary> /// <summary>Asset interceptors added or removed since the last tick.</summary>
private readonly List<AssetInterceptorChange> ReloadAssetInterceptorsQueue = new List<AssetInterceptorChange>(); private readonly List<AssetInterceptorChange> ReloadAssetInterceptorsQueue = new List<AssetInterceptorChange>();
@ -145,6 +142,10 @@ namespace StardewModdingAPI.Framework
/// <remarks>This is initialized after the game starts. This is accessed directly because it's not part of the normal class model.</remarks> /// <remarks>This is initialized after the game starts. This is accessed directly because it's not part of the normal class model.</remarks>
internal static DeprecationManager DeprecationManager { get; private set; } internal static DeprecationManager DeprecationManager { get; private set; }
/// <summary>The singleton instance.</summary>
/// <remarks>This is only intended for use by external code like the Error Handler mod.</remarks>
internal static SCore Instance { get; private set; }
/// <summary>The number of update ticks which have already executed. This is similar to <see cref="Game1.ticks"/>, but incremented more consistently for every tick.</summary> /// <summary>The number of update ticks which have already executed. This is similar to <see cref="Game1.ticks"/>, but incremented more consistently for every tick.</summary>
internal static uint TicksElapsed { get; private set; } internal static uint TicksElapsed { get; private set; }
@ -157,6 +158,8 @@ namespace StardewModdingAPI.Framework
/// <param name="writeToConsole">Whether to output log messages to the console.</param> /// <param name="writeToConsole">Whether to output log messages to the console.</param>
public SCore(string modsPath, bool writeToConsole) public SCore(string modsPath, bool writeToConsole)
{ {
SCore.Instance = this;
// init paths // init paths
this.VerifyPath(modsPath); this.VerifyPath(modsPath);
this.VerifyPath(Constants.LogDir); this.VerifyPath(Constants.LogDir);
@ -245,12 +248,7 @@ namespace StardewModdingAPI.Framework
// apply game patches // apply game patches
new GamePatcher(this.Monitor).Apply( new GamePatcher(this.Monitor).Apply(
new EventErrorPatch(this.LogManager.MonitorForGame), new LoadContextPatch(this.Reflection, this.OnLoadStageChanged)
new DialogueErrorPatch(this.LogManager.MonitorForGame, this.Reflection),
new ObjectErrorPatch(),
new LoadContextPatch(this.Reflection, this.OnLoadStageChanged),
new LoadErrorPatch(this.Monitor, this.OnSaveContentRemoved),
new ScheduleErrorPatch(this.LogManager.MonitorForGame)
); );
// add exit handler // add exit handler
@ -517,15 +515,6 @@ namespace StardewModdingAPI.Framework
this.ScreenCommandQueue.GetValueForScreen(screenId).Add(Tuple.Create(command, name, args)); this.ScreenCommandQueue.GetValueForScreen(screenId).Add(Tuple.Create(command, name, args));
} }
/*********
** Show in-game warnings (for main player only)
*********/
// save content removed
if (this.IsSaveContentRemoved && Context.IsWorldReady)
{
this.IsSaveContentRemoved = false;
Game1.addHUDMessage(new HUDMessage(this.Translator.Get("warn.invalid-content-removed"), HUDMessage.error_type));
}
/********* /*********
** Run game update ** Run game update
@ -1105,12 +1094,6 @@ namespace StardewModdingAPI.Framework
Game1.CustomData[migrationKey] = Constants.ApiVersion.ToString(); Game1.CustomData[migrationKey] = Constants.ApiVersion.ToString();
} }
/// <summary>Raised after custom content is removed from the save data to avoid a crash.</summary>
internal void OnSaveContentRemoved()
{
this.IsSaveContentRemoved = true;
}
/// <summary>A callback invoked before <see cref="Game1.newDayAfterFade"/> runs.</summary> /// <summary>A callback invoked before <see cref="Game1.newDayAfterFade"/> runs.</summary>
protected void OnNewDayAfterFade() protected void OnNewDayAfterFade()
{ {

View File

@ -1,4 +1,5 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("SMAPI.Tests")] [assembly: InternalsVisibleTo("SMAPI.Tests")]
[assembly: InternalsVisibleTo("ErrorHandler")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // Moq for unit testing [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // Moq for unit testing

View File

@ -113,6 +113,7 @@ copy all the settings, or you may cause bugs due to overridden changes in future
*/ */
"SuppressUpdateChecks": [ "SuppressUpdateChecks": [
"SMAPI.ConsoleCommands", "SMAPI.ConsoleCommands",
"SMAPI.ErrorHandler",
"SMAPI.SaveBackup" "SMAPI.SaveBackup"
] ]
} }

View File

@ -1,10 +1,6 @@
{ {
// error messages
"warn.invalid-content-removed": "Ungültiger Inhalt wurde entfernt, um einen Absturz zu verhindern (siehe SMAPI Konsole für weitere Informationen).",
// short date format for SDate // short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2) // tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}} {{day}}", "generic.date": "{{season}} {{day}}",
"generic.date-with-year": "{{season}} {{day}} im Jahr {{year}}" "generic.date-with-year": "{{season}} {{day}} im Jahr {{year}}"
} }

View File

@ -1,7 +1,4 @@
{ {
// error messages
"warn.invalid-content-removed": "Invalid content was removed to prevent a crash (see the SMAPI console for info).",
// short date format for SDate // short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2) // tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}} {{day}}", "generic.date": "{{season}} {{day}}",

View File

@ -1,7 +1,4 @@
{ {
// error messages
"warn.invalid-content-removed": "Se ha quitado contenido inválido para evitar un cierre forzoso (revisa la consola de SMAPI para más información).",
// short date format for SDate // short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2) // tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{seasonLowercase}} {{day}}", "generic.date": "{{seasonLowercase}} {{day}}",

View File

@ -1,7 +1,4 @@
{ {
// error messages
"warn.invalid-content-removed": "Le contenu non valide a été supprimé afin d'éviter un plantage (voir la console de SMAPI pour plus d'informations).",
// short date format for SDate // short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2) // tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{day}} {{seasonLowercase}}", "generic.date": "{{day}} {{seasonLowercase}}",

View File

@ -1,7 +1,4 @@
{ {
// error messages
"warn.invalid-content-removed": "Érvénytelen elemek kerültek eltávolításra, hogy a játék ne omoljon össze (további információk a SMAPI konzolon).",
// short date format for SDate // short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2) // tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}} {{day}}", "generic.date": "{{season}} {{day}}",

View File

@ -1,7 +1,4 @@
{ {
// error messages
"warn.invalid-content-removed": "Contenuto non valido rimosso per prevenire un crash (Guarda la console di SMAPI per maggiori informazioni).",
// short date format for SDate // short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2) // tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{day}} {{season}}", "generic.date": "{{day}} {{season}}",

View File

@ -1,7 +1,4 @@
{ {
// error messages
"warn.invalid-content-removed": "クラッシュを防ぐために無効なコンテンツを取り除きました 詳細はSMAPIコンソールを参照",
// short date format for SDate // short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2) // tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}} {{day}}日", "generic.date": "{{season}} {{day}}日",

View File

@ -1,7 +1,4 @@
{ {
// error messages
"warn.invalid-content-removed": "충돌을 방지하기 위해 잘못된 컨텐츠가 제거되었습니다 (자세한 내용은 SMAPI 콘솔 참조).",
// short date format for SDate // short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2) // tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}} {{day}}", "generic.date": "{{season}} {{day}}",

View File

@ -1,7 +1,4 @@
{ {
// error messages
"warn.invalid-content-removed": "Conteúdo inválido foi removido para prevenir uma falha (veja o console do SMAPI para mais informações).",
// short date format for SDate // short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2) // tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}} {{day}}", "generic.date": "{{season}} {{day}}",

View File

@ -1,7 +1,4 @@
{ {
// error messages
"warn.invalid-content-removed": "Недопустимое содержимое было удалено, чтобы предотвратить сбой (см. информацию в консоли SMAPI)",
// short date format for SDate // short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2) // tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}}, {{day}}-е число", "generic.date": "{{season}}, {{day}}-е число",

View File

@ -1,7 +1,4 @@
{ {
// error messages
"warn.invalid-content-removed": "Yanlış paketlenmiş bir içerik, oyunun çökmemesi için yüklenmedi (SMAPI konsol penceresinde detaylı bilgi mevcut).",
// short date format for SDate // short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2) // tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{day}} {{season}}", "generic.date": "{{day}} {{season}}",

View File

@ -1,7 +1,4 @@
{ {
// error messages
"warn.invalid-content-removed": "非法内容已移除以防游戏闪退查看SMAPI控制台获得更多信息",
// short date format for SDate // short date format for SDate
// tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2) // tokens: {{day}} (like 15), {{season}} (like Spring), {{seasonLowercase}} (like spring), {{year}} (like 2)
"generic.date": "{{season}}{{day}}日", "generic.date": "{{season}}{{day}}日",