migrate to the new Harmony patch pattern used in my mods
That improves validation and error-handling.
This commit is contained in:
parent
4074f697d7
commit
948c800a98
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace StardewModdingAPI.Internal.Patching
|
||||
{
|
||||
/// <summary>Provides base implementation logic for <see cref="IPatcher"/> instances.</summary>
|
||||
internal abstract class BasePatcher : IPatcher
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <inheritdoc />
|
||||
public abstract void Apply(Harmony harmony, IMonitor monitor);
|
||||
|
||||
|
||||
/*********
|
||||
** Protected methods
|
||||
*********/
|
||||
/// <summary>Get a method and assert that it was found.</summary>
|
||||
/// <typeparam name="TTarget">The type containing the method.</typeparam>
|
||||
/// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
|
||||
protected ConstructorInfo RequireConstructor<TTarget>(params Type[] parameters)
|
||||
{
|
||||
return PatchHelper.RequireConstructor<TTarget>(parameters);
|
||||
}
|
||||
|
||||
/// <summary>Get a method and assert that it was found.</summary>
|
||||
/// <typeparam name="TTarget">The type containing the method.</typeparam>
|
||||
/// <param name="name">The method name.</param>
|
||||
/// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
|
||||
/// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param>
|
||||
protected MethodInfo RequireMethod<TTarget>(string name, Type[] parameters = null, Type[] generics = null)
|
||||
{
|
||||
return PatchHelper.RequireMethod<TTarget>(name, parameters, generics);
|
||||
}
|
||||
|
||||
/// <summary>Get a Harmony patch method on the current patcher instance.</summary>
|
||||
/// <param name="name">The method name.</param>
|
||||
/// <param name="priority">The patch priority to apply, usually specified using Harmony's <see cref="Priority"/> enum, or <c>null</c> to keep the default value.</param>
|
||||
protected HarmonyMethod GetHarmonyMethod(string name, int? priority = null)
|
||||
{
|
||||
var method = new HarmonyMethod(
|
||||
AccessTools.Method(this.GetType(), name)
|
||||
?? throw new InvalidOperationException($"Can't find patcher method {PatchHelper.GetMethodString(this.GetType(), name)}.")
|
||||
);
|
||||
|
||||
if (priority.HasValue)
|
||||
method.priority = priority.Value;
|
||||
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace StardewModdingAPI.Internal.Patching
|
||||
{
|
||||
/// <summary>Simplifies applying <see cref="IPatcher"/> instances to the game.</summary>
|
||||
internal static class HarmonyPatcher
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Apply the given Harmony patchers.</summary>
|
||||
/// <param name="id">The mod ID applying the patchers.</param>
|
||||
/// <param name="monitor">The monitor with which to log any errors.</param>
|
||||
/// <param name="patchers">The patchers to apply.</param>
|
||||
public static Harmony Apply(string id, IMonitor monitor, params IPatcher[] patchers)
|
||||
{
|
||||
Harmony harmony = new Harmony(id);
|
||||
|
||||
foreach (IPatcher patcher in patchers)
|
||||
{
|
||||
try
|
||||
{
|
||||
patcher.Apply(harmony, monitor);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
monitor.Log($"Couldn't apply runtime patch '{patcher.GetType().Name}' to the game. Some SMAPI features may not work correctly. See log file for details.", LogLevel.Error);
|
||||
monitor.Log($"Technical details:\n{ex.GetLogSummary()}");
|
||||
}
|
||||
}
|
||||
|
||||
return harmony;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using HarmonyLib;
|
||||
|
||||
namespace StardewModdingAPI.Internal.Patching
|
||||
{
|
||||
/// <summary>A set of Harmony patches to apply.</summary>
|
||||
internal interface IPatcher
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Apply the Harmony patches for this instance.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
/// <param name="monitor">The monitor with which to log any errors.</param>
|
||||
public void Apply(Harmony harmony, IMonitor monitor);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace StardewModdingAPI.Internal.Patching
|
||||
{
|
||||
/// <summary>Provides utility methods for patching game code with Harmony.</summary>
|
||||
internal static class PatchHelper
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get a constructor and assert that it was found.</summary>
|
||||
/// <typeparam name="TTarget">The type containing the method.</typeparam>
|
||||
/// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
|
||||
/// <exception cref="InvalidOperationException">The type has no matching constructor.</exception>
|
||||
public static ConstructorInfo RequireConstructor<TTarget>(Type[] parameters = null)
|
||||
{
|
||||
return
|
||||
AccessTools.Constructor(typeof(TTarget), parameters)
|
||||
?? throw new InvalidOperationException($"Can't find constructor {PatchHelper.GetMethodString(typeof(TTarget), null, parameters)} to patch.");
|
||||
}
|
||||
|
||||
/// <summary>Get a method and assert that it was found.</summary>
|
||||
/// <typeparam name="TTarget">The type containing the method.</typeparam>
|
||||
/// <param name="name">The method name.</param>
|
||||
/// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
|
||||
/// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param>
|
||||
/// <exception cref="InvalidOperationException">The type has no matching method.</exception>
|
||||
public static MethodInfo RequireMethod<TTarget>(string name, Type[] parameters = null, Type[] generics = null)
|
||||
{
|
||||
return
|
||||
AccessTools.Method(typeof(TTarget), name, parameters, generics)
|
||||
?? throw new InvalidOperationException($"Can't find method {PatchHelper.GetMethodString(typeof(TTarget), name, parameters, generics)} to patch.");
|
||||
}
|
||||
|
||||
/// <summary>Get a human-readable representation of a method target.</summary>
|
||||
/// <param name="type">The type containing the method.</param>
|
||||
/// <param name="name">The method name, or <c>null</c> for a constructor.</param>
|
||||
/// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
|
||||
/// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param>
|
||||
public static string GetMethodString(Type type, string name, Type[] parameters = null, Type[] generics = null)
|
||||
{
|
||||
StringBuilder str = new();
|
||||
|
||||
// type
|
||||
str.Append(type.FullName);
|
||||
|
||||
// method name (if not constructor)
|
||||
if (name != null)
|
||||
{
|
||||
str.Append('.');
|
||||
str.Append(name);
|
||||
}
|
||||
|
||||
// generics
|
||||
if (generics?.Any() == true)
|
||||
{
|
||||
str.Append('<');
|
||||
str.Append(string.Join(", ", generics.Select(p => p.FullName)));
|
||||
str.Append('>');
|
||||
}
|
||||
|
||||
// parameters
|
||||
if (parameters?.Any() == true)
|
||||
{
|
||||
str.Append('(');
|
||||
str.Append(string.Join(", ", parameters.Select(p => p.FullName)));
|
||||
str.Append(')');
|
||||
}
|
||||
|
||||
return str.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' < '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
<HasSharedItems>true</HasSharedItems>
|
||||
<SharedGUID>6c16e948-3e5c-47a7-bf4b-07a7469a87a5</SharedGUID>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<Import_RootNamespace>SMAPI.Internal.Patching</Import_RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)BasePatcher.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)HarmonyPatcher.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)IPatcher.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)PatchHelper.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>6c16e948-3e5c-47a7-bf4b-07a7469a87a5</ProjectGuid>
|
||||
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
|
||||
<PropertyGroup />
|
||||
<Import Project="SMAPI.Internal.Patching.projitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace StardewModdingAPI.Internal
|
||||
{
|
||||
/// <summary>Provides extension methods for handling exceptions.</summary>
|
||||
internal static class ExceptionExtensions
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Get a string representation of an exception suitable for writing to the error log.</summary>
|
||||
/// <param name="exception">The error to summarize.</param>
|
||||
public static string GetLogSummary(this Exception exception)
|
||||
{
|
||||
switch (exception)
|
||||
{
|
||||
case TypeLoadException ex:
|
||||
return $"Failed loading type '{ex.TypeName}': {exception}";
|
||||
|
||||
case ReflectionTypeLoadException ex:
|
||||
string summary = exception.ToString();
|
||||
foreach (Exception childEx in ex.LoaderExceptions)
|
||||
summary += $"\n\n{childEx.GetLogSummary()}";
|
||||
return summary;
|
||||
|
||||
default:
|
||||
return exception.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Get the lowest exception in an exception stack.</summary>
|
||||
/// <param name="exception">The exception from which to search.</param>
|
||||
public static Exception GetInnermostException(this Exception exception)
|
||||
{
|
||||
while (exception.InnerException != null)
|
||||
exception = exception.InnerException;
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,5 +14,6 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ConsoleLogLevel.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\IConsoleWriter.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\MonitorColorScheme.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ExceptionExtensions.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using StardewModdingAPI.Events;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
using StardewModdingAPI.Mods.ErrorHandler.Patches;
|
||||
using StardewValley;
|
||||
|
||||
|
@ -28,7 +28,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler
|
|||
IMonitor monitorForGame = this.GetMonitorForGame();
|
||||
|
||||
// apply patches
|
||||
new GamePatcher(this.Monitor).Apply(
|
||||
HarmonyPatcher.Apply(this.ModManifest.UniqueID, this.Monitor,
|
||||
new DialoguePatcher(monitorForGame, this.Helper.Reflection),
|
||||
new DictionaryPatcher(this.Helper.Reflection),
|
||||
new EventPatcher(monitorForGame),
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Internal;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
using StardewValley;
|
||||
|
||||
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>Harmony patches for <see cref="Dialogue"/> which intercept 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>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
internal class DialoguePatcher : IHarmonyPatch
|
||||
internal class DialoguePatcher : BasePatcher
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
|
@ -36,11 +36,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Harmony harmony)
|
||||
public override void Apply(Harmony harmony, IMonitor monitor)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) }),
|
||||
finalizer: new HarmonyMethod(this.GetType(), nameof(DialoguePatcher.Finalize_Dialogue_Constructor))
|
||||
original: this.RequireConstructor<Dialogue>(typeof(string), typeof(NPC)),
|
||||
finalizer: this.GetHarmonyMethod(nameof(DialoguePatcher.Finalize_Constructor))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -48,13 +48,13 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>The method to call after the Dialogue constructor.</summary>
|
||||
/// <summary>The method to call when the Dialogue constructor throws an exception.</summary>
|
||||
/// <param name="__instance">The instance being patched.</param>
|
||||
/// <param name="masterDialogue">The dialogue being parsed.</param>
|
||||
/// <param name="speaker">The NPC for which the dialogue is being parsed.</param>
|
||||
/// <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_Dialogue_Constructor(Dialogue __instance, string masterDialogue, NPC speaker, Exception __exception)
|
||||
private static Exception Finalize_Constructor(Dialogue __instance, string masterDialogue, NPC speaker, Exception __exception)
|
||||
{
|
||||
if (__exception != null)
|
||||
{
|
||||
|
|
|
@ -2,18 +2,18 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
using StardewValley.GameData;
|
||||
using StardewValley.GameData.HomeRenovations;
|
||||
using StardewValley.GameData.Movies;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
||||
{
|
||||
/// <summary>A Harmony patch for <see cref="Dictionary{TKey,TValue}"/> which adds the accessed key to <see cref="KeyNotFoundException"/> exceptions.</summary>
|
||||
/// <summary>Harmony patches for <see cref="Dictionary{TKey,TValue}"/> which add the accessed key to <see cref="KeyNotFoundException"/> exceptions.</summary>
|
||||
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
internal class DictionaryPatcher : IHarmonyPatch
|
||||
internal class DictionaryPatcher : BasePatcher
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
|
@ -33,7 +33,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Harmony harmony)
|
||||
public override void Apply(Harmony harmony, IMonitor monitor)
|
||||
{
|
||||
Type[] keyTypes = { typeof(int), typeof(string) };
|
||||
Type[] valueTypes = { typeof(int), typeof(string), typeof(HomeRenovation), typeof(MovieData), typeof(SpecialOrderData) };
|
||||
|
@ -45,8 +45,8 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
|
||||
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(dictionaryType, "get_Item"),
|
||||
finalizer: new HarmonyMethod(this.GetType(), nameof(DictionaryPatcher.Finalize_GetItem))
|
||||
original: AccessTools.Method(dictionaryType, "get_Item") ?? throw new InvalidOperationException($"Can't find method {PatchHelper.GetMethodString(dictionaryType, "get_Item")} to patch."),
|
||||
finalizer: this.GetHarmonyMethod(nameof(DictionaryPatcher.Finalize_GetItem))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -63,19 +63,13 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
private static Exception Finalize_GetItem(object key, Exception __exception)
|
||||
{
|
||||
if (__exception is KeyNotFoundException)
|
||||
AddKeyTo(__exception, key?.ToString());
|
||||
{
|
||||
DictionaryPatcher.Reflection
|
||||
.GetField<string>(__exception, "_message")
|
||||
.SetValue($"{__exception.Message}\nkey: '{key}'");
|
||||
}
|
||||
|
||||
return __exception;
|
||||
}
|
||||
|
||||
/// <summary>Add the accessed key to an exception message.</summary>
|
||||
/// <param name="exception">The exception to modify.</param>
|
||||
/// <param name="key">The dictionary key.</param>
|
||||
private static void AddKeyTo(Exception exception, string key)
|
||||
{
|
||||
DictionaryPatcher.Reflection
|
||||
.GetField<string>(exception, "_message")
|
||||
.SetValue($"{exception.Message}\nkey: '{key}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
||||
|
@ -10,7 +10,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
internal class EventPatcher : IHarmonyPatch
|
||||
internal class EventPatcher : BasePatcher
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
|
@ -30,11 +30,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Harmony harmony)
|
||||
public override void Apply(Harmony harmony, IMonitor monitor)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(Event), nameof(Event.LogErrorAndHalt)),
|
||||
postfix: new HarmonyMethod(this.GetType(), nameof(EventPatcher.After_Event_LogErrorAndHalt))
|
||||
original: this.RequireMethod<Event>(nameof(Event.LogErrorAndHalt)),
|
||||
postfix: this.GetHarmonyMethod(nameof(EventPatcher.After_LogErrorAndHalt))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
*********/
|
||||
/// <summary>The method to call after <see cref="Event.LogErrorAndHalt"/>.</summary>
|
||||
/// <param name="e">The exception being logged.</param>
|
||||
private static void After_Event_LogErrorAndHalt(Exception e)
|
||||
private static void After_LogErrorAndHalt(Exception e)
|
||||
{
|
||||
EventPatcher.MonitorForGame.Log(e.ToString(), LogLevel.Error);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
using StardewValley;
|
||||
using xTile;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
||||
{
|
||||
/// <summary>Harmony patches for <see cref="GameLocation.checkEventPrecondition"/> and <see cref="GameLocation.updateSeasonalTileSheets"/> which intercept errors instead of crashing.</summary>
|
||||
/// <summary>Harmony patches for <see cref="GameLocation"/> which intercept errors instead of crashing.</summary>
|
||||
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
internal class GameLocationPatcher : IHarmonyPatch
|
||||
internal class GameLocationPatcher : BasePatcher
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
|
@ -31,15 +31,15 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Harmony harmony)
|
||||
public override void Apply(Harmony harmony, IMonitor monitor)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.checkEventPrecondition)),
|
||||
finalizer: new HarmonyMethod(this.GetType(), nameof(GameLocationPatcher.Finalize_GameLocation_CheckEventPrecondition))
|
||||
original: this.RequireMethod<GameLocation>(nameof(GameLocation.checkEventPrecondition)),
|
||||
finalizer: this.GetHarmonyMethod(nameof(GameLocationPatcher.Finalize_CheckEventPrecondition))
|
||||
);
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.updateSeasonalTileSheets)),
|
||||
finalizer: new HarmonyMethod(this.GetType(), nameof(GameLocationPatcher.Before_GameLocation_UpdateSeasonalTileSheets))
|
||||
original: this.RequireMethod<GameLocation>(nameof(GameLocation.updateSeasonalTileSheets)),
|
||||
finalizer: this.GetHarmonyMethod(nameof(GameLocationPatcher.Finalize_UpdateSeasonalTileSheets))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -47,12 +47,12 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>The method to call instead of GameLocation.checkEventPrecondition.</summary>
|
||||
/// <summary>The method to call when <see cref="GameLocation.checkEventPrecondition"/> throws an exception.</summary>
|
||||
/// <param name="__result">The return value of the original method.</param>
|
||||
/// <param name="precondition">The precondition to be parsed.</param>
|
||||
/// <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_GameLocation_CheckEventPrecondition(ref int __result, string precondition, Exception __exception)
|
||||
private static Exception Finalize_CheckEventPrecondition(ref int __result, string precondition, Exception __exception)
|
||||
{
|
||||
if (__exception != null)
|
||||
{
|
||||
|
@ -63,12 +63,12 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
return null;
|
||||
}
|
||||
|
||||
/// <summary>The method to call instead of <see cref="GameLocation.updateSeasonalTileSheets"/>.</summary>
|
||||
/// <summary>The method to call when <see cref="GameLocation.updateSeasonalTileSheets"/> throws an exception.</summary>
|
||||
/// <param name="__instance">The instance being patched.</param>
|
||||
/// <param name="map">The map whose tilesheets to update.</param>
|
||||
/// <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 Before_GameLocation_UpdateSeasonalTileSheets(GameLocation __instance, Map map, Exception __exception)
|
||||
private static Exception Finalize_UpdateSeasonalTileSheets(GameLocation __instance, Map map, Exception __exception)
|
||||
{
|
||||
if (__exception != null)
|
||||
GameLocationPatcher.MonitorForGame.Log($"Failed updating seasonal tilesheets for location '{__instance.NameOrUniqueName}': \n{__exception}", LogLevel.Error);
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
using StardewValley;
|
||||
using StardewValley.Menus;
|
||||
using SObject = StardewValley.Object;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
||||
{
|
||||
/// <summary>A Harmony patch for <see cref="IClickableMenu"/> which intercepts crashes due to invalid items.</summary>
|
||||
/// <summary>Harmony patches for <see cref="IClickableMenu"/> which intercept crashes due to invalid items.</summary>
|
||||
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
internal class IClickableMenuPatcher : IHarmonyPatch
|
||||
internal class IClickableMenuPatcher : BasePatcher
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <inheritdoc />
|
||||
public void Apply(Harmony harmony)
|
||||
public override void Apply(Harmony harmony, IMonitor monitor)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawToolTip)),
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(IClickableMenuPatcher.Before_IClickableMenu_DrawTooltip))
|
||||
original: this.RequireMethod<IClickableMenu>(nameof(IClickableMenu.drawToolTip)),
|
||||
prefix: this.GetHarmonyMethod(nameof(IClickableMenuPatcher.Before_DrawTooltip))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
/// <summary>The method to call instead of <see cref="IClickableMenu.drawToolTip"/>.</summary>
|
||||
/// <param name="hoveredItem">The item for which to draw a tooltip.</param>
|
||||
/// <returns>Returns whether to execute the original method.</returns>
|
||||
private static bool Before_IClickableMenu_DrawTooltip(Item hoveredItem)
|
||||
private static bool Before_DrawTooltip(Item hoveredItem)
|
||||
{
|
||||
// invalid edible item cause crash when drawing tooltips
|
||||
if (hoveredItem is SObject obj && obj.Edibility != -300 && !Game1.objectInformation.ContainsKey(obj.ParentSheetIndex))
|
||||
|
|
|
@ -2,17 +2,17 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Internal;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
||||
{
|
||||
/// <summary>A Harmony patch for <see cref="NPC.parseMasterSchedule"/> which intercepts crashes due to invalid schedule data.</summary>
|
||||
/// <summary>Harmony patches for <see cref="NPC"/> which intercept 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>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
internal class NpcPatcher : IHarmonyPatch
|
||||
internal class NpcPatcher : BasePatcher
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
|
@ -32,16 +32,16 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Harmony harmony)
|
||||
public override void Apply(Harmony harmony, IMonitor monitor)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Property(typeof(NPC), nameof(NPC.CurrentDialogue)).GetMethod,
|
||||
finalizer: new HarmonyMethod(this.GetType(), nameof(NpcPatcher.Finalize_NPC_CurrentDialogue))
|
||||
original: this.RequireMethod<NPC>($"get_{nameof(NPC.CurrentDialogue)}"),
|
||||
finalizer: this.GetHarmonyMethod(nameof(NpcPatcher.Finalize_CurrentDialogue))
|
||||
);
|
||||
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(NPC), nameof(NPC.parseMasterSchedule)),
|
||||
finalizer: new HarmonyMethod(this.GetType(), nameof(NpcPatcher.Finalize_NPC_parseMasterSchedule))
|
||||
original: this.RequireMethod<NPC>(nameof(NPC.parseMasterSchedule)),
|
||||
finalizer: this.GetHarmonyMethod(nameof(NpcPatcher.Finalize_ParseMasterSchedule))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -49,12 +49,12 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>The method to call after <see cref="NPC.CurrentDialogue"/>.</summary>
|
||||
/// <summary>The method to call when <see cref="NPC.CurrentDialogue"/> throws an exception.</summary>
|
||||
/// <param name="__instance">The instance being patched.</param>
|
||||
/// <param name="__result">The return value of the original method.</param>
|
||||
/// <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_NPC_CurrentDialogue(NPC __instance, ref Stack<Dialogue> __result, Exception __exception)
|
||||
private static Exception Finalize_CurrentDialogue(NPC __instance, ref Stack<Dialogue> __result, Exception __exception)
|
||||
{
|
||||
if (__exception == null)
|
||||
return null;
|
||||
|
@ -71,7 +71,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
/// <param name="__result">The patched method's return value.</param>
|
||||
/// <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_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary<int, SchedulePathDescription> __result, Exception __exception)
|
||||
private static Exception Finalize_ParseMasterSchedule(string rawData, NPC __instance, ref Dictionary<int, SchedulePathDescription> __result, Exception __exception)
|
||||
{
|
||||
if (__exception != null)
|
||||
{
|
||||
|
|
|
@ -2,34 +2,34 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
using StardewValley;
|
||||
using SObject = StardewValley.Object;
|
||||
|
||||
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>Harmony patches for <see cref="SObject"/> which intercept crashes due to invalid items.</summary>
|
||||
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
internal class ObjectPatcher : IHarmonyPatch
|
||||
internal class ObjectPatcher : BasePatcher
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <inheritdoc />
|
||||
public void Apply(Harmony harmony)
|
||||
public override void Apply(Harmony harmony, IMonitor monitor)
|
||||
{
|
||||
// object.getDescription
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(SObject), nameof(SObject.getDescription)),
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(ObjectPatcher.Before_Object_GetDescription))
|
||||
original: this.RequireMethod<SObject>(nameof(SObject.getDescription)),
|
||||
prefix: this.GetHarmonyMethod(nameof(ObjectPatcher.Before_Object_GetDescription))
|
||||
);
|
||||
|
||||
// object.getDisplayName
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(SObject), "loadDisplayName"),
|
||||
finalizer: new HarmonyMethod(this.GetType(), nameof(ObjectPatcher.Finalize_Object_loadDisplayName))
|
||||
original: this.RequireMethod<SObject>("loadDisplayName"),
|
||||
finalizer: this.GetHarmonyMethod(nameof(ObjectPatcher.Finalize_Object_loadDisplayName))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,18 +4,18 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
using StardewValley;
|
||||
using StardewValley.Buildings;
|
||||
using StardewValley.Locations;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
||||
{
|
||||
/// <summary>A Harmony patch for <see cref="SaveGame"/> which prevents some errors due to broken save data.</summary>
|
||||
/// <summary>Harmony patches for <see cref="SaveGame"/> which prevent 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>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
internal class SaveGamePatcher : IHarmonyPatch
|
||||
internal class SaveGamePatcher : BasePatcher
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
|
@ -39,13 +39,12 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
SaveGamePatcher.OnContentRemoved = onContentRemoved;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Harmony harmony)
|
||||
public override void Apply(Harmony harmony, IMonitor monitor)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(SaveGame), nameof(SaveGame.loadDataToLocations)),
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(SaveGamePatcher.Before_SaveGame_LoadDataToLocations))
|
||||
original: this.RequireMethod<SaveGame>(nameof(SaveGame.loadDataToLocations)),
|
||||
prefix: this.GetHarmonyMethod(nameof(SaveGamePatcher.Before_LoadDataToLocations))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -56,7 +55,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
/// <summary>The method to call instead of <see cref="SaveGame.loadDataToLocations"/>.</summary>
|
||||
/// <param name="gamelocations">The game locations being loaded.</param>
|
||||
/// <returns>Returns whether to execute the original method.</returns>
|
||||
private static bool Before_SaveGame_LoadDataToLocations(List<GameLocation> gamelocations)
|
||||
private static bool Before_LoadDataToLocations(List<GameLocation> gamelocations)
|
||||
{
|
||||
bool removedAny =
|
||||
SaveGamePatcher.RemoveBrokenBuildings(gamelocations)
|
||||
|
|
|
@ -2,27 +2,27 @@ using System;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using HarmonyLib;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
||||
{
|
||||
/// <summary>Harmony patch for <see cref="SpriteBatch"/> to validate textures earlier.</summary>
|
||||
/// <summary>Harmony patches for <see cref="SpriteBatch"/> which validate textures earlier.</summary>
|
||||
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
internal class SpriteBatchPatcher : IHarmonyPatch
|
||||
internal class SpriteBatchPatcher : BasePatcher
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <inheritdoc />
|
||||
public void Apply(Harmony harmony)
|
||||
public override void Apply(Harmony harmony, IMonitor monitor)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: Constants.GameFramework == GameFramework.Xna
|
||||
? AccessTools.Method(typeof(SpriteBatch), "InternalDraw")
|
||||
: AccessTools.Method(typeof(SpriteBatch), "CheckValid", new[] { typeof(Texture2D) }),
|
||||
postfix: new HarmonyMethod(this.GetType(), nameof(SpriteBatchPatcher.After_SpriteBatch_CheckValid))
|
||||
? this.RequireMethod<SpriteBatch>("InternalDraw")
|
||||
: this.RequireMethod<SpriteBatch>("CheckValid", new[] { typeof(Texture2D) }),
|
||||
postfix: this.GetHarmonyMethod(nameof(SpriteBatchPatcher.After_CheckValid))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -31,13 +31,13 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
** Private methods
|
||||
*********/
|
||||
#if SMAPI_FOR_XNA
|
||||
/// <summary>The method to call instead of <see cref="SpriteBatch.InternalDraw"/>.</summary>
|
||||
/// <summary>The method to call after <see cref="SpriteBatch.InternalDraw"/>.</summary>
|
||||
/// <param name="texture">The texture to validate.</param>
|
||||
#else
|
||||
/// <summary>The method to call instead of <see cref="SpriteBatch.CheckValid"/>.</summary>
|
||||
/// <summary>The method to call after <see cref="SpriteBatch.CheckValid"/>.</summary>
|
||||
/// <param name="texture">The texture to validate.</param>
|
||||
#endif
|
||||
private static void After_SpriteBatch_CheckValid(Texture2D texture)
|
||||
private static void After_CheckValid(Texture2D texture)
|
||||
{
|
||||
if (texture?.IsDisposed == true)
|
||||
throw new ObjectDisposedException("Cannot draw this texture because it's disposed.");
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
using StardewValley;
|
||||
|
||||
namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
||||
|
@ -10,17 +10,17 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
internal class UtilityPatcher : IHarmonyPatch
|
||||
internal class UtilityPatcher : BasePatcher
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <inheritdoc />
|
||||
public void Apply(Harmony harmony)
|
||||
public override void Apply(Harmony harmony, IMonitor monitor)
|
||||
{
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(Utility), nameof(Utility.getItemFromStandardTextDescription)),
|
||||
finalizer: new HarmonyMethod(this.GetType(), nameof(UtilityPatcher.Finalize_Utility_GetItemFromStandardTextDescription))
|
||||
original: this.RequireMethod<Utility>(nameof(Utility.getItemFromStandardTextDescription)),
|
||||
finalizer: this.GetHarmonyMethod(nameof(UtilityPatcher.Finalize_GetItemFromStandardTextDescription))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -28,12 +28,12 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
|
|||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>The method to call instead of <see cref="Utility.getItemFromStandardTextDescription"/>.</summary>
|
||||
/// <summary>The method to call when <see cref="Utility.getItemFromStandardTextDescription"/> throws an exception.</summary>
|
||||
/// <param name="description">The item text description to parse.</param>
|
||||
/// <param name="delimiter">The delimiter by which to split the text description.</param>
|
||||
/// <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_Utility_GetItemFromStandardTextDescription(string description, char delimiter, ref Exception __exception)
|
||||
private static Exception Finalize_GetItemFromStandardTextDescription(string description, char delimiter, ref Exception __exception)
|
||||
{
|
||||
return __exception != null
|
||||
? new FormatException($"Failed to parse item text description \"{description}\" with delimiter \"{delimiter}\".", __exception)
|
||||
|
|
|
@ -47,4 +47,5 @@
|
|||
</ItemGroup>
|
||||
|
||||
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
|
||||
<Import Project="..\SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems" Label="Shared" />
|
||||
</Project>
|
||||
|
|
|
@ -52,6 +52,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mods", "Mods", "{AE9A4D46-E
|
|||
EndProject
|
||||
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SMAPI.Internal", "SMAPI.Internal\SMAPI.Internal.shproj", "{85208F8D-6FD1-4531-BE05-7142490F59FE}"
|
||||
EndProject
|
||||
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SMAPI.Internal.Patching", "SMAPI.Internal.Patching\SMAPI.Internal.Patching.shproj", "{6C16E948-3E5C-47A7-BF4B-07A7469A87A5}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.ModBuildConfig.Analyzer.Tests", "SMAPI.ModBuildConfig.Analyzer.Tests\SMAPI.ModBuildConfig.Analyzer.Tests.csproj", "{680B2641-81EA-467C-86A5-0E81CDC57ED0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Tests", "SMAPI.Tests\SMAPI.Tests.csproj", "{AA95884B-7097-476E-92C8-D0500DE9D6D1}"
|
||||
|
@ -86,10 +88,13 @@ Global
|
|||
GlobalSection(SharedMSBuildProjectFiles) = preSolution
|
||||
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.Patching\SMAPI.Internal.Patching.projitems*{491e775b-ead0-44d4-b6ca-f1fc3e316d33}*SharedItemsImports = 5
|
||||
SMAPI.Internal\SMAPI.Internal.projitems*{491e775b-ead0-44d4-b6ca-f1fc3e316d33}*SharedItemsImports = 5
|
||||
SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems*{6c16e948-3e5c-47a7-bf4b-07a7469a87a5}*SharedItemsImports = 13
|
||||
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*{cd53ad6f-97f4-4872-a212-50c2a0fd3601}*SharedItemsImports = 5
|
||||
SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems*{e6da2198-7686-4f1d-b312-4a4dc70884c0}*SharedItemsImports = 5
|
||||
SMAPI.Internal\SMAPI.Internal.projitems*{e6da2198-7686-4f1d-b312-4a4dc70884c0}*SharedItemsImports = 5
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -156,6 +161,7 @@ Global
|
|||
{EB35A917-67B9-4EFA-8DFC-4FB49B3949BB} = {86C452BE-D2D8-45B4-B63F-E329EB06CEDA}
|
||||
{5947303D-3512-413A-9009-7AC43F5D3513} = {EB35A917-67B9-4EFA-8DFC-4FB49B3949BB}
|
||||
{85208F8D-6FD1-4531-BE05-7142490F59FE} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}
|
||||
{6C16E948-3E5C-47A7-BF4B-07A7469A87A5} = {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}
|
||||
{0634EA4C-3B8F-42DB-AEA6-CA9E4EF6E92F} = {AE9A4D46-E910-4293-8BC4-673F85FFF6A5}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using StardewModdingAPI.Internal;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Content
|
||||
{
|
||||
|
|
|
@ -9,6 +9,7 @@ using StardewModdingAPI.Framework.Content;
|
|||
using StardewModdingAPI.Framework.Exceptions;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Framework.Utilities;
|
||||
using StardewModdingAPI.Internal;
|
||||
using StardewValley;
|
||||
using xTile;
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ using Microsoft.Xna.Framework.Content;
|
|||
using Microsoft.Xna.Framework.Graphics;
|
||||
using StardewModdingAPI.Framework.Exceptions;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Internal;
|
||||
using StardewModdingAPI.Toolkit.Serialization;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
using StardewValley;
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using StardewModdingAPI.Events;
|
||||
using StardewModdingAPI.Internal;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Events
|
||||
{
|
||||
|
|
|
@ -14,6 +14,9 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>Provides extension methods for SMAPI's internal use.</summary>
|
||||
internal static class InternalExtensions
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/****
|
||||
** IMonitor
|
||||
****/
|
||||
|
@ -54,38 +57,6 @@ namespace StardewModdingAPI.Framework
|
|||
@event.Raise(Singleton<TEventArgs>.Instance);
|
||||
}
|
||||
|
||||
/****
|
||||
** Exceptions
|
||||
****/
|
||||
/// <summary>Get a string representation of an exception suitable for writing to the error log.</summary>
|
||||
/// <param name="exception">The error to summarize.</param>
|
||||
public static string GetLogSummary(this Exception exception)
|
||||
{
|
||||
switch (exception)
|
||||
{
|
||||
case TypeLoadException ex:
|
||||
return $"Failed loading type '{ex.TypeName}': {exception}";
|
||||
|
||||
case ReflectionTypeLoadException ex:
|
||||
string summary = exception.ToString();
|
||||
foreach (Exception childEx in ex.LoaderExceptions)
|
||||
summary += $"\n\n{childEx.GetLogSummary()}";
|
||||
return summary;
|
||||
|
||||
default:
|
||||
return exception.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Get the lowest exception in an exception stack.</summary>
|
||||
/// <param name="exception">The exception from which to search.</param>
|
||||
public static Exception GetInnermostException(this Exception exception)
|
||||
{
|
||||
while (exception.InnerException != null)
|
||||
exception = exception.InnerException;
|
||||
return exception;
|
||||
}
|
||||
|
||||
/****
|
||||
** ReaderWriterLockSlim
|
||||
****/
|
||||
|
|
|
@ -9,6 +9,7 @@ using System.Threading;
|
|||
using StardewModdingAPI.Framework.Commands;
|
||||
using StardewModdingAPI.Framework.Models;
|
||||
using StardewModdingAPI.Framework.ModLoading;
|
||||
using StardewModdingAPI.Internal;
|
||||
using StardewModdingAPI.Internal.ConsoleWriting;
|
||||
using StardewModdingAPI.Toolkit.Framework.ModData;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
using System;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Patching
|
||||
{
|
||||
/// <summary>Encapsulates applying Harmony patches to the game.</summary>
|
||||
internal class GamePatcher
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>Encapsulates monitoring and logging.</summary>
|
||||
private readonly IMonitor Monitor;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="monitor">Encapsulates monitoring and logging.</param>
|
||||
public GamePatcher(IMonitor monitor)
|
||||
{
|
||||
this.Monitor = monitor;
|
||||
}
|
||||
|
||||
/// <summary>Apply all loaded patches to the game.</summary>
|
||||
/// <param name="patches">The patches to apply.</param>
|
||||
public void Apply(params IHarmonyPatch[] patches)
|
||||
{
|
||||
Harmony harmony = new Harmony("SMAPI");
|
||||
foreach (IHarmonyPatch patch in patches)
|
||||
{
|
||||
try
|
||||
{
|
||||
patch.Apply(harmony);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Monitor.Log($"Couldn't apply runtime patch '{patch.GetType().Name}' to the game. Some SMAPI features may not work correctly. See log file for details.", LogLevel.Error);
|
||||
this.Monitor.Log(ex.GetLogSummary(), LogLevel.Trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
using HarmonyLib;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Patching
|
||||
{
|
||||
/// <summary>A Harmony patch to apply.</summary>
|
||||
internal interface IHarmonyPatch
|
||||
{
|
||||
/*********
|
||||
** Methods
|
||||
*********/
|
||||
/// <summary>Apply the Harmony patch.</summary>
|
||||
/// <param name="harmony">The Harmony instance.</param>
|
||||
void Apply(Harmony harmony);
|
||||
}
|
||||
}
|
|
@ -31,13 +31,14 @@ using StardewModdingAPI.Framework.Models;
|
|||
using StardewModdingAPI.Framework.ModHelpers;
|
||||
using StardewModdingAPI.Framework.ModLoading;
|
||||
using StardewModdingAPI.Framework.Networking;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Framework.Rendering;
|
||||
using StardewModdingAPI.Framework.Serialization;
|
||||
using StardewModdingAPI.Framework.StateTracking.Comparers;
|
||||
using StardewModdingAPI.Framework.StateTracking.Snapshots;
|
||||
using StardewModdingAPI.Framework.Utilities;
|
||||
using StardewModdingAPI.Internal;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
using StardewModdingAPI.Patches;
|
||||
using StardewModdingAPI.Toolkit;
|
||||
using StardewModdingAPI.Toolkit.Framework.Clients.WebApi;
|
||||
|
@ -254,7 +255,7 @@ namespace StardewModdingAPI.Framework
|
|||
|
||||
// apply game patches
|
||||
MiniMonoModHotfix.Apply();
|
||||
new GamePatcher(this.Monitor).Apply(
|
||||
HarmonyPatcher.Apply("SMAPI", this.Monitor,
|
||||
new Game1Patcher(this.Reflection, this.OnLoadStageChanged),
|
||||
new TitleMenuPatcher(this.OnLoadStageChanged)
|
||||
);
|
||||
|
|
|
@ -11,6 +11,7 @@ using StardewModdingAPI.Framework.Input;
|
|||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Framework.StateTracking.Snapshots;
|
||||
using StardewModdingAPI.Framework.Utilities;
|
||||
using StardewModdingAPI.Internal;
|
||||
using StardewModdingAPI.Utilities;
|
||||
using StardewValley;
|
||||
using StardewValley.BellsAndWhistles;
|
||||
|
|
|
@ -8,6 +8,7 @@ using Netcode;
|
|||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Framework.ContentManagers;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Internal;
|
||||
using StardewModdingAPI.Toolkit.Utilities;
|
||||
using StardewValley;
|
||||
using StardewValley.BellsAndWhistles;
|
||||
|
|
|
@ -2,19 +2,19 @@ using System;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Enums;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
using StardewValley;
|
||||
using StardewValley.Menus;
|
||||
using StardewValley.Minigames;
|
||||
|
||||
namespace StardewModdingAPI.Patches
|
||||
{
|
||||
/// <summary>Harmony patches which notify SMAPI for save creation load stages.</summary>
|
||||
/// <summary>Harmony patches for <see cref="Game1"/> which notify SMAPI for save load stages.</summary>
|
||||
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
internal class Game1Patcher : IHarmonyPatch
|
||||
internal class Game1Patcher : BasePatcher
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
|
@ -42,25 +42,25 @@ namespace StardewModdingAPI.Patches
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Harmony harmony)
|
||||
public override void Apply(Harmony harmony, IMonitor monitor)
|
||||
{
|
||||
// detect CreatedInitialLocations and SaveAddedLocations
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(Game1), nameof(Game1.AddModNPCs)),
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(Game1Patcher.Before_Game1_AddModNPCs))
|
||||
original: this.RequireMethod<Game1>(nameof(Game1.AddModNPCs)),
|
||||
prefix: this.GetHarmonyMethod(nameof(Game1Patcher.Before_AddModNpcs))
|
||||
);
|
||||
|
||||
// detect CreatedLocations, and track IsInLoadForNewGame
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(Game1), nameof(Game1.loadForNewGame)),
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(Game1Patcher.Before_Game1_LoadForNewGame)),
|
||||
postfix: new HarmonyMethod(this.GetType(), nameof(Game1Patcher.After_Game1_LoadForNewGame))
|
||||
original: this.RequireMethod<Game1>(nameof(Game1.loadForNewGame)),
|
||||
prefix: this.GetHarmonyMethod(nameof(Game1Patcher.Before_LoadForNewGame)),
|
||||
postfix: this.GetHarmonyMethod(nameof(Game1Patcher.After_LoadForNewGame))
|
||||
);
|
||||
|
||||
// detect ReturningToTitle
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(Game1), nameof(Game1.CleanupReturningToTitle)),
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(Game1Patcher.Before_Game1_CleanupReturningToTitle))
|
||||
original: this.RequireMethod<Game1>(nameof(Game1.CleanupReturningToTitle)),
|
||||
prefix: this.GetHarmonyMethod(nameof(Game1Patcher.Before_CleanupReturningToTitle))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -68,10 +68,10 @@ namespace StardewModdingAPI.Patches
|
|||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Called before <see cref="Game1.AddModNPCs"/>.</summary>
|
||||
/// <summary>The method to call before <see cref="Game1.AddModNPCs"/>.</summary>
|
||||
/// <returns>Returns whether to execute the original method.</returns>
|
||||
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
|
||||
private static bool Before_Game1_AddModNPCs()
|
||||
private static bool Before_AddModNpcs()
|
||||
{
|
||||
// When this method is called from Game1.loadForNewGame, it happens right after adding the vanilla
|
||||
// locations but before initializing them.
|
||||
|
@ -86,27 +86,27 @@ namespace StardewModdingAPI.Patches
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Called before <see cref="Game1.CleanupReturningToTitle"/>.</summary>
|
||||
/// <summary>The method to call before <see cref="Game1.CleanupReturningToTitle"/>.</summary>
|
||||
/// <returns>Returns whether to execute the original method.</returns>
|
||||
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
|
||||
private static bool Before_Game1_CleanupReturningToTitle()
|
||||
private static bool Before_CleanupReturningToTitle()
|
||||
{
|
||||
Game1Patcher.OnStageChanged(LoadStage.ReturningToTitle);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Called before <see cref="Game1.loadForNewGame"/>.</summary>
|
||||
/// <summary>The method to call before <see cref="Game1.loadForNewGame"/>.</summary>
|
||||
/// <returns>Returns whether to execute the original method.</returns>
|
||||
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
|
||||
private static bool Before_Game1_LoadForNewGame()
|
||||
private static bool Before_LoadForNewGame()
|
||||
{
|
||||
Game1Patcher.IsInLoadForNewGame = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Called after <see cref="Game1.loadForNewGame"/>.</summary>
|
||||
/// <summary>The method to call after <see cref="Game1.loadForNewGame"/>.</summary>
|
||||
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
|
||||
private static void After_Game1_LoadForNewGame()
|
||||
private static void After_LoadForNewGame()
|
||||
{
|
||||
Game1Patcher.IsInLoadForNewGame = false;
|
||||
|
||||
|
|
|
@ -2,16 +2,16 @@ using System;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using HarmonyLib;
|
||||
using StardewModdingAPI.Enums;
|
||||
using StardewModdingAPI.Framework.Patching;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
using StardewValley.Menus;
|
||||
|
||||
namespace StardewModdingAPI.Patches
|
||||
{
|
||||
/// <summary>Harmony patches which notify SMAPI for save creation load stages.</summary>
|
||||
/// <summary>Harmony patches for <see cref="TitleMenu"/> which notify SMAPI when a new character was created.</summary>
|
||||
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
|
||||
internal class TitleMenuPatcher : IHarmonyPatch
|
||||
internal class TitleMenuPatcher : BasePatcher
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
|
@ -31,12 +31,11 @@ namespace StardewModdingAPI.Patches
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(Harmony harmony)
|
||||
public override void Apply(Harmony harmony, IMonitor monitor)
|
||||
{
|
||||
// detect CreatedBasicInfo
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(TitleMenu), nameof(TitleMenu.createdNewCharacter)),
|
||||
prefix: new HarmonyMethod(this.GetType(), nameof(TitleMenuPatcher.Before_TitleMenu_CreatedNewCharacter))
|
||||
original: this.RequireMethod<TitleMenu>(nameof(TitleMenu.createdNewCharacter)),
|
||||
prefix: this.GetHarmonyMethod(nameof(TitleMenuPatcher.Before_CreatedNewCharacter))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -44,10 +43,10 @@ namespace StardewModdingAPI.Patches
|
|||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Called before <see cref="TitleMenu.createdNewCharacter"/>.</summary>
|
||||
/// <summary>The method to call before <see cref="TitleMenu.createdNewCharacter"/>.</summary>
|
||||
/// <returns>Returns whether to execute the original method.</returns>
|
||||
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
|
||||
private static bool Before_TitleMenu_CreatedNewCharacter()
|
||||
private static bool Before_CreatedNewCharacter()
|
||||
{
|
||||
TitleMenuPatcher.OnStageChanged(LoadStage.CreatedBasicInfo);
|
||||
return true;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("SMAPI.Tests")]
|
||||
[assembly: InternalsVisibleTo("ErrorHandler")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // Moq for unit testing
|
||||
|
|
|
@ -76,4 +76,5 @@
|
|||
</ItemGroup>
|
||||
|
||||
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
|
||||
<Import Project="..\SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems" Label="Shared" />
|
||||
</Project>
|
||||
|
|
Loading…
Reference in New Issue