Merge branch 'harmony-2' into develop

This commit is contained in:
Jesse Plamondon-Willard 2021-07-21 22:49:53 -04:00
commit 92bcb8405c
No known key found for this signature in database
GPG Key ID: CF8B1456B3E29F49
26 changed files with 3327 additions and 447 deletions

Binary file not shown.

3130
build/0Harmony.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -38,6 +38,7 @@
<Copy SourceFiles="$(TargetDir)\SMAPI.config.json" DestinationFiles="$(GamePath)\smapi-internal\config.json" /> <Copy SourceFiles="$(TargetDir)\SMAPI.config.json" DestinationFiles="$(GamePath)\smapi-internal\config.json" />
<Copy SourceFiles="$(TargetDir)\SMAPI.metadata.json" DestinationFiles="$(GamePath)\smapi-internal\metadata.json" /> <Copy SourceFiles="$(TargetDir)\SMAPI.metadata.json" DestinationFiles="$(GamePath)\smapi-internal\metadata.json" />
<Copy SourceFiles="$(TargetDir)\0Harmony.dll" DestinationFolder="$(GamePath)\smapi-internal" /> <Copy SourceFiles="$(TargetDir)\0Harmony.dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\0Harmony.xml" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.dll" DestinationFolder="$(GamePath)\smapi-internal" /> <Copy SourceFiles="$(TargetDir)\Mono.Cecil.dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\Newtonsoft.Json.dll" DestinationFolder="$(GamePath)\smapi-internal" /> <Copy SourceFiles="$(TargetDir)\Newtonsoft.Json.dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\TMXTile.dll" DestinationFolder="$(GamePath)\smapi-internal" /> <Copy SourceFiles="$(TargetDir)\TMXTile.dll" DestinationFolder="$(GamePath)\smapi-internal" />

View File

@ -45,6 +45,7 @@
<Copy SourceFiles="$(SmapiBin)\StardewModdingAPI.xml" DestinationFolder="$(PackagePath)\bundle" /> <Copy SourceFiles="$(SmapiBin)\StardewModdingAPI.xml" DestinationFolder="$(PackagePath)\bundle" />
<Copy SourceFiles="$(SmapiBin)\steam_appid.txt" DestinationFolder="$(PackagePath)\bundle" /> <Copy SourceFiles="$(SmapiBin)\steam_appid.txt" DestinationFolder="$(PackagePath)\bundle" />
<Copy SourceFiles="$(SmapiBin)\0Harmony.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" /> <Copy SourceFiles="$(SmapiBin)\0Harmony.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(SmapiBin)\0Harmony.xml" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(SmapiBin)\Mono.Cecil.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" /> <Copy SourceFiles="$(SmapiBin)\Mono.Cecil.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(SmapiBin)\Newtonsoft.Json.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" /> <Copy SourceFiles="$(SmapiBin)\Newtonsoft.Json.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(SmapiBin)\TMXTile.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" /> <Copy SourceFiles="$(SmapiBin)\TMXTile.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />

View File

@ -1,11 +1,9 @@
← [README](README.md) ← [README](README.md)
# Release notes # Release notes
<!-- ## Upcoming release
## Future release
* For mod authors: * For mod authors:
* Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). * Updated Harmony 1.2.0.1 to 2.1.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).
-->
## Upcoming release ## Upcoming release
* For players: * For players:

View File

@ -59,7 +59,6 @@ flag | purpose
`SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled for Windows; if not set, the code assumes Linux/macOS. Set automatically in `common.targets`. `SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled for Windows; if not set, the code assumes Linux/macOS. Set automatically in `common.targets`.
`SMAPI_FOR_WINDOWS_64BIT_HACK` | Whether SMAPI is being [compiled for Windows with a 64-bit Linux version of the game](https://github.com/Pathoschild/SMAPI/issues/767). This is highly specialized and shouldn't be used in most cases. False by default. `SMAPI_FOR_WINDOWS_64BIT_HACK` | Whether SMAPI is being [compiled for Windows with a 64-bit Linux version of the game](https://github.com/Pathoschild/SMAPI/issues/767). This is highly specialized and shouldn't be used in most cases. False by default.
`SMAPI_FOR_XNA` | Whether SMAPI is being compiled for XNA Framework; if not set, the code assumes MonoGame. Set automatically in `common.targets` with the same value as `SMAPI_FOR_WINDOWS` (unless `SMAPI_FOR_WINDOWS_64BIT_HACK` is set). `SMAPI_FOR_XNA` | Whether SMAPI is being compiled for XNA Framework; if not set, the code assumes MonoGame. Set automatically in `common.targets` with the same value as `SMAPI_FOR_WINDOWS` (unless `SMAPI_FOR_WINDOWS_64BIT_HACK` is set).
`HARMONY_2` | Whether to enable experimental Harmony 2.0 support and rewrite existing Harmony 1._x_ mods for compatibility. Note that you need to replace `build/0Harmony.dll` with a Harmony 2.0 build (or switch to a package reference) to use this flag.
## For SMAPI developers ## For SMAPI developers
### Compiling from source ### Compiling from source

View File

@ -1,15 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using StardewModdingAPI.Framework.Patching;
using StardewValley;
#if HARMONY_2
using HarmonyLib; using HarmonyLib;
using StardewModdingAPI.Framework; using StardewModdingAPI.Framework;
#else using StardewModdingAPI.Framework.Patching;
using System.Reflection; using StardewValley;
using Harmony;
#endif
namespace StardewModdingAPI.Mods.ErrorHandler.Patches namespace StardewModdingAPI.Mods.ErrorHandler.Patches
{ {
@ -41,9 +36,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
DialogueErrorPatch.Reflection = reflector; DialogueErrorPatch.Reflection = reflector;
} }
/// <inheritdoc /> /// <inheritdoc />
#if HARMONY_2
public void Apply(Harmony harmony) public void Apply(Harmony harmony)
{ {
harmony.Patch( harmony.Patch(
@ -55,25 +48,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
finalizer: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Finalize_NPC_CurrentDialogue)) finalizer: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Finalize_NPC_CurrentDialogue))
); );
} }
#else
public void Apply(HarmonyInstance harmony)
{
harmony.Patch(
original: AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) }),
prefix: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Before_Dialogue_Constructor))
);
harmony.Patch(
original: AccessTools.Property(typeof(NPC), nameof(NPC.CurrentDialogue)).GetMethod,
prefix: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Before_NPC_CurrentDialogue))
);
}
#endif
/********* /*********
** Private methods ** Private methods
*********/ *********/
#if HARMONY_2
/// <summary>The method to call after the Dialogue constructor.</summary> /// <summary>The method to call after the Dialogue constructor.</summary>
/// <param name="__instance">The instance being patched.</param> /// <param name="__instance">The instance being patched.</param>
/// <param name="masterDialogue">The dialogue being parsed.</param> /// <param name="masterDialogue">The dialogue being parsed.</param>
@ -113,72 +92,5 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
return null; return null;
} }
#else
/// <summary>The method to call instead of the Dialogue constructor.</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>
/// <returns>Returns whether to execute the original method.</returns>
private static bool Before_Dialogue_Constructor(Dialogue __instance, string masterDialogue, NPC speaker)
{
// get private members
bool nameArraysTranslated = DialogueErrorPatch.Reflection.GetField<bool>(typeof(Dialogue), "nameArraysTranslated").GetValue();
IReflectedMethod translateArraysOfStrings = DialogueErrorPatch.Reflection.GetMethod(typeof(Dialogue), "TranslateArraysOfStrings");
IReflectedMethod parseDialogueString = DialogueErrorPatch.Reflection.GetMethod(__instance, "parseDialogueString");
IReflectedMethod checkForSpecialDialogueAttributes = DialogueErrorPatch.Reflection.GetMethod(__instance, "checkForSpecialDialogueAttributes");
// replicate base constructor
__instance.dialogues ??= new List<string>();
// duplicate code with try..catch
try
{
if (!nameArraysTranslated)
translateArraysOfStrings.Invoke();
__instance.speaker = speaker;
parseDialogueString.Invoke(masterDialogue);
checkForSpecialDialogueAttributes.Invoke();
}
catch (Exception baseEx) when (baseEx.InnerException is TargetInvocationException invocationEx && invocationEx.InnerException is Exception ex)
{
string name = !string.IsNullOrWhiteSpace(speaker?.Name) ? speaker.Name : null;
DialogueErrorPatch.MonitorForGame.Log($"Failed parsing dialogue string{(name != null ? $" for {name}" : "")}:\n{masterDialogue}\n{ex}", LogLevel.Error);
parseDialogueString.Invoke("...");
checkForSpecialDialogueAttributes.Invoke();
}
return false;
}
/// <summary>The method to call instead of <see cref="NPC.CurrentDialogue"/>.</summary>
/// <param name="__instance">The instance being patched.</param>
/// <param name="__result">The return value of the original method.</param>
/// <param name="__originalMethod">The method being wrapped.</param>
/// <returns>Returns whether to execute the original method.</returns>
private static bool Before_NPC_CurrentDialogue(NPC __instance, ref Stack<Dialogue> __result, MethodInfo __originalMethod)
{
const string key = nameof(DialogueErrorPatch.Before_NPC_CurrentDialogue);
if (!PatchHelper.StartIntercept(key))
return true;
try
{
__result = (Stack<Dialogue>)__originalMethod.Invoke(__instance, new object[0]);
return false;
}
catch (TargetInvocationException ex)
{
DialogueErrorPatch.MonitorForGame.Log($"Failed loading current dialogue for NPC {__instance.Name}:\n{ex.InnerException ?? ex}", LogLevel.Error);
__result = new Stack<Dialogue>();
return false;
}
finally
{
PatchHelper.StopIntercept(key);
}
}
#endif
} }
} }

View File

@ -1,10 +1,6 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
#if HARMONY_2
using HarmonyLib; using HarmonyLib;
#else
using Harmony;
#endif
using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Patching;
using StardewValley; using StardewValley;
@ -34,11 +30,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
} }
/// <inheritdoc /> /// <inheritdoc />
#if HARMONY_2
public void Apply(Harmony harmony) public void Apply(Harmony harmony)
#else
public void Apply(HarmonyInstance harmony)
#endif
{ {
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(Event), nameof(Event.LogErrorAndHalt)), original: AccessTools.Method(typeof(Event), nameof(Event.LogErrorAndHalt)),

View File

@ -1,11 +1,6 @@
using System.Diagnostics.CodeAnalysis;
#if HARMONY_2
using System; using System;
using System.Diagnostics.CodeAnalysis;
using HarmonyLib; using HarmonyLib;
#else
using System.Reflection;
using Harmony;
#endif
using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Patching;
using StardewValley; using StardewValley;
using xTile; using xTile;
@ -36,37 +31,22 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
} }
/// <inheritdoc /> /// <inheritdoc />
#if HARMONY_2
public void Apply(Harmony harmony) public void Apply(Harmony harmony)
{ {
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.checkEventPrecondition)), original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.checkEventPrecondition)),
finalizer: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Finalize_GameLocation_CheckEventPrecondition)) finalizer: new HarmonyMethod(this.GetType(), nameof(GameLocationPatches.Finalize_GameLocation_CheckEventPrecondition))
);
harmony.Patch(
original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.updateSeasonalTileSheets)),
finalizer: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Before_GameLocation_UpdateSeasonalTileSheets))
);
}
#else
public void Apply(HarmonyInstance harmony)
{
harmony.Patch(
original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.checkEventPrecondition)),
prefix: new HarmonyMethod(this.GetType(), nameof(GameLocationPatches.Before_GameLocation_CheckEventPrecondition))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.updateSeasonalTileSheets)), original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.updateSeasonalTileSheets)),
prefix: new HarmonyMethod(this.GetType(), nameof(GameLocationPatches.Before_GameLocation_UpdateSeasonalTileSheets)) finalizer: new HarmonyMethod(this.GetType(), nameof(GameLocationPatches.Before_GameLocation_UpdateSeasonalTileSheets))
); );
} }
#endif
/********* /*********
** Private methods ** Private methods
*********/ *********/
#if HARMONY_2
/// <summary>The method to call instead of GameLocation.checkEventPrecondition.</summary> /// <summary>The method to call instead of GameLocation.checkEventPrecondition.</summary>
/// <param name="__result">The return value of the original method.</param> /// <param name="__result">The return value of the original method.</param>
/// <param name="precondition">The precondition to be parsed.</param> /// <param name="precondition">The precondition to be parsed.</param>
@ -77,43 +57,12 @@ harmony.Patch(
if (__exception != null) if (__exception != null)
{ {
__result = -1; __result = -1;
EventErrorPatch.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{__exception.InnerException}", LogLevel.Error); GameLocationPatches.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{__exception.InnerException}", LogLevel.Error);
} }
return null; return null;
} }
#else
/// <summary>The method to call instead of <see cref="GameLocation.checkEventPrecondition"/>.</summary>
/// <param name="__instance">The instance being patched.</param>
/// <param name="__result">The return value of the original method.</param>
/// <param name="precondition">The precondition to be parsed.</param>
/// <param name="__originalMethod">The method being wrapped.</param>
/// <returns>Returns whether to execute the original method.</returns>
private static bool Before_GameLocation_CheckEventPrecondition(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod)
{
const string key = nameof(GameLocationPatches.Before_GameLocation_CheckEventPrecondition);
if (!PatchHelper.StartIntercept(key))
return true;
try
{
__result = (int)__originalMethod.Invoke(__instance, new object[] { precondition });
return false;
}
catch (TargetInvocationException ex)
{
__result = -1;
GameLocationPatches.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{ex.InnerException}", LogLevel.Error);
return false;
}
finally
{
PatchHelper.StopIntercept(key);
}
}
#endif
#if HARMONY_2
/// <summary>The method to call instead of <see cref="GameLocation.updateSeasonalTileSheets"/>.</summary> /// <summary>The method to call instead of <see cref="GameLocation.updateSeasonalTileSheets"/>.</summary>
/// <param name="__instance">The instance being patched.</param> /// <param name="__instance">The instance being patched.</param>
/// <param name="map">The map whose tilesheets to update.</param> /// <param name="map">The map whose tilesheets to update.</param>
@ -126,33 +75,5 @@ harmony.Patch(
return null; return null;
} }
#else
/// <summary>The method to call instead of <see cref="GameLocation.updateSeasonalTileSheets"/>.</summary>
/// <param name="__instance">The instance being patched.</param>
/// <param name="map">The map whose tilesheets to update.</param>
/// <param name="__originalMethod">The method being wrapped.</param>
/// <returns>Returns whether to execute the original method.</returns>
private static bool Before_GameLocation_UpdateSeasonalTileSheets(GameLocation __instance, Map map, MethodInfo __originalMethod)
{
const string key = nameof(GameLocationPatches.Before_GameLocation_UpdateSeasonalTileSheets);
if (!PatchHelper.StartIntercept(key))
return true;
try
{
__originalMethod.Invoke(__instance, new object[] { map });
return false;
}
catch (TargetInvocationException ex)
{
GameLocationPatches.MonitorForGame.Log($"Failed updating seasonal tilesheets for location '{__instance.NameOrUniqueName}'. Technical details:\n{ex.InnerException}", LogLevel.Error);
return false;
}
finally
{
PatchHelper.StopIntercept(key);
}
}
#endif
} }
} }

View File

@ -2,11 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
#if HARMONY_2
using HarmonyLib; using HarmonyLib;
#else
using Harmony;
#endif
using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Patching;
using StardewValley; using StardewValley;
@ -45,11 +41,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
/// <inheritdoc /> /// <inheritdoc />
#if HARMONY_2
public void Apply(Harmony harmony) public void Apply(Harmony harmony)
#else
public void Apply(HarmonyInstance harmony)
#endif
{ {
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(SaveGame), nameof(SaveGame.loadDataToLocations)), original: AccessTools.Method(typeof(SaveGame), nameof(SaveGame.loadDataToLocations)),

View File

@ -1,16 +1,11 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using HarmonyLib;
using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Patching;
using StardewValley; using StardewValley;
using StardewValley.Menus; using StardewValley.Menus;
using SObject = StardewValley.Object; using SObject = StardewValley.Object;
#if HARMONY_2
using System;
using HarmonyLib;
#else
using System.Reflection;
using Harmony;
#endif
namespace StardewModdingAPI.Mods.ErrorHandler.Patches namespace StardewModdingAPI.Mods.ErrorHandler.Patches
{ {
@ -24,11 +19,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
** Public methods ** Public methods
*********/ *********/
/// <inheritdoc /> /// <inheritdoc />
#if HARMONY_2
public void Apply(Harmony harmony) public void Apply(Harmony harmony)
#else
public void Apply(HarmonyInstance harmony)
#endif
{ {
// object.getDescription // object.getDescription
harmony.Patch( harmony.Patch(
@ -39,11 +30,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
// object.getDisplayName // object.getDisplayName
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(SObject), "loadDisplayName"), original: AccessTools.Method(typeof(SObject), "loadDisplayName"),
#if HARMONY_2
finalizer: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Finalize_Object_loadDisplayName)) finalizer: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Finalize_Object_loadDisplayName))
#else
prefix: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Before_Object_loadDisplayName))
#endif
); );
// IClickableMenu.drawToolTip // IClickableMenu.drawToolTip
@ -73,7 +60,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
return true; return true;
} }
#if HARMONY_2
/// <summary>The method to call after <see cref="StardewValley.Object.loadDisplayName"/>.</summary> /// <summary>The method to call after <see cref="StardewValley.Object.loadDisplayName"/>.</summary>
/// <param name="__result">The patched method's return value.</param> /// <param name="__result">The patched method's return value.</param>
/// <param name="__exception">The exception thrown by the wrapped method, if any.</param> /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
@ -88,38 +74,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
return __exception; return __exception;
} }
#else
/// <summary>The method to call instead of <see cref="StardewValley.Object.loadDisplayName"/>.</summary>
/// <param name="__instance">The instance being patched.</param>
/// <param name="__result">The patched method's return value.</param>
/// <param name="__originalMethod">The method being wrapped.</param>
/// <returns>Returns whether to execute the original method.</returns>
private static bool Before_Object_loadDisplayName(SObject __instance, ref string __result, MethodInfo __originalMethod)
{
const string key = nameof(ObjectErrorPatch.Before_Object_loadDisplayName);
if (!PatchHelper.StartIntercept(key))
return true;
try
{
__result = (string)__originalMethod.Invoke(__instance, new object[0]);
return false;
}
catch (TargetInvocationException ex) when (ex.InnerException is KeyNotFoundException)
{
__result = "???";
return false;
}
catch
{
return true;
}
finally
{
PatchHelper.StopIntercept(key);
}
}
#endif
/// <summary>The method to call instead of <see cref="IClickableMenu.drawToolTip"/>.</summary> /// <summary>The method to call instead of <see cref="IClickableMenu.drawToolTip"/>.</summary>
/// <param name="hoveredItem">The item for which to draw a tooltip.</param> /// <param name="hoveredItem">The item for which to draw a tooltip.</param>

View File

@ -1,15 +1,10 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using StardewModdingAPI.Framework.Patching;
using StardewValley;
#if HARMONY_2
using System;
using HarmonyLib; using HarmonyLib;
using StardewModdingAPI.Framework; using StardewModdingAPI.Framework;
#else using StardewModdingAPI.Framework.Patching;
using System.Reflection; using StardewValley;
using Harmony;
#endif
namespace StardewModdingAPI.Mods.ErrorHandler.Patches namespace StardewModdingAPI.Mods.ErrorHandler.Patches
{ {
@ -37,19 +32,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
} }
/// <inheritdoc /> /// <inheritdoc />
#if HARMONY_2
public void Apply(Harmony harmony) public void Apply(Harmony harmony)
#else
public void Apply(HarmonyInstance harmony)
#endif
{ {
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(NPC), nameof(NPC.parseMasterSchedule)), original: AccessTools.Method(typeof(NPC), nameof(NPC.parseMasterSchedule)),
#if HARMONY_2
finalizer: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Finalize_NPC_parseMasterSchedule)) finalizer: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Finalize_NPC_parseMasterSchedule))
#else
prefix: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Before_NPC_parseMasterSchedule))
#endif
); );
} }
@ -57,7 +44,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
/********* /*********
** Private methods ** Private methods
*********/ *********/
#if HARMONY_2
/// <summary>The method to call instead of <see cref="NPC.parseMasterSchedule"/>.</summary> /// <summary>The method to call instead of <see cref="NPC.parseMasterSchedule"/>.</summary>
/// <param name="rawData">The raw schedule data to parse.</param> /// <param name="rawData">The raw schedule data to parse.</param>
/// <param name="__instance">The instance being patched.</param> /// <param name="__instance">The instance being patched.</param>
@ -74,35 +60,5 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
return null; return null;
} }
#else
/// <summary>The method to call instead of <see cref="NPC.parseMasterSchedule"/>.</summary>
/// <param name="rawData">The raw schedule data to parse.</param>
/// <param name="__instance">The instance being patched.</param>
/// <param name="__result">The patched method's return value.</param>
/// <param name="__originalMethod">The method being wrapped.</param>
/// <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)
{
const string key = nameof(ScheduleErrorPatch.Before_NPC_parseMasterSchedule);
if (!PatchHelper.StartIntercept(key))
return true;
try
{
__result = (Dictionary<int, SchedulePathDescription>)__originalMethod.Invoke(__instance, new object[] { rawData });
return false;
}
catch (TargetInvocationException ex)
{
ScheduleErrorPatch.MonitorForGame.Log($"Failed parsing schedule for NPC {__instance.Name}:\n{rawData}\n{ex.InnerException ?? ex}", LogLevel.Error);
__result = new Dictionary<int, SchedulePathDescription>();
return false;
}
finally
{
PatchHelper.StopIntercept(key);
}
}
#endif
} }
} }

View File

@ -1,10 +1,6 @@
#if HARMONY_2
using HarmonyLib;
#else
using Harmony;
#endif
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using HarmonyLib;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Patching;
@ -20,11 +16,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
** Public methods ** Public methods
*********/ *********/
/// <inheritdoc /> /// <inheritdoc />
#if HARMONY_2
public void Apply(Harmony harmony) public void Apply(Harmony harmony)
#else
public void Apply(HarmonyInstance harmony)
#endif
{ {
harmony.Patch( harmony.Patch(
original: Constants.GameFramework == GameFramework.Xna original: Constants.GameFramework == GameFramework.Xna

View File

@ -1,12 +1,6 @@
#if HARMONY_2
using System;
using HarmonyLib;
#else
using Harmony;
#endif
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection; using HarmonyLib;
using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Patching;
using StardewValley; using StardewValley;
@ -22,7 +16,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
** Public methods ** Public methods
*********/ *********/
/// <inheritdoc /> /// <inheritdoc />
#if HARMONY_2
public void Apply(Harmony harmony) public void Apply(Harmony harmony)
{ {
harmony.Patch( harmony.Patch(
@ -30,21 +23,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
finalizer: new HarmonyMethod(this.GetType(), nameof(UtilityErrorPatches.Finalize_Utility_GetItemFromStandardTextDescription)) finalizer: new HarmonyMethod(this.GetType(), nameof(UtilityErrorPatches.Finalize_Utility_GetItemFromStandardTextDescription))
); );
} }
#else
public void Apply(HarmonyInstance harmony)
{
harmony.Patch(
original: AccessTools.Method(typeof(Utility), nameof(Utility.getItemFromStandardTextDescription)),
prefix: new HarmonyMethod(this.GetType(), nameof(UtilityErrorPatches.Before_Utility_GetItemFromStandardTextDescription))
);
}
#endif
/********* /*********
** Private methods ** Private methods
*********/ *********/
#if HARMONY_2
/// <summary>The method to call instead of <see cref="Utility.getItemFromStandardTextDescription"/>.</summary> /// <summary>The method to call instead of <see cref="Utility.getItemFromStandardTextDescription"/>.</summary>
/// <param name="description">The item text description to parse.</param> /// <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="delimiter">The delimiter by which to split the text description.</param>
@ -56,34 +39,5 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
? new FormatException($"Failed to parse item text description \"{description}\" with delimiter \"{delimiter}\".", __exception) ? new FormatException($"Failed to parse item text description \"{description}\" with delimiter \"{delimiter}\".", __exception)
: null; : null;
} }
#else
/// <summary>The method to call instead of <see cref="Utility.getItemFromStandardTextDescription"/>.</summary>
/// <param name="__result">The return value of the original method.</param>
/// <param name="description">The item text description to parse.</param>
/// <param name="who">The player for which the item is being parsed.</param>
/// <param name="delimiter">The delimiter by which to split the text description.</param>
/// <param name="__originalMethod">The method being wrapped.</param>
/// <returns>Returns whether to execute the original method.</returns>
private static bool Before_Utility_GetItemFromStandardTextDescription(ref Item __result, string description, Farmer who, char delimiter, MethodInfo __originalMethod)
{
const string key = nameof(UtilityErrorPatches.Before_Utility_GetItemFromStandardTextDescription);
if (!PatchHelper.StartIntercept(key))
return true;
try
{
__result = (Item)__originalMethod.Invoke(null, new object[] { description, who, delimiter });
return false;
}
catch (TargetInvocationException ex)
{
throw new FormatException($"Failed to parse item text description \"{description}\" with delimiter \"{delimiter}\".", ex.InnerException);
}
finally
{
PatchHelper.StopIntercept(key);
}
}
#endif
} }
} }

View File

@ -3,25 +3,13 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
#if HARMONY_2
using HarmonyLib; using HarmonyLib;
#else
using Harmony;
#endif
namespace StardewModdingAPI.Framework.Commands namespace StardewModdingAPI.Framework.Commands
{ {
/// <summary>The 'harmony_summary' SMAPI console command.</summary> /// <summary>The 'harmony_summary' SMAPI console command.</summary>
internal class HarmonySummaryCommand : IInternalCommand internal class HarmonySummaryCommand : IInternalCommand
{ {
#if !HARMONY_2
/*********
** Fields
*********/
/// <summary>The Harmony instance through which to fetch patch info.</summary>
private readonly HarmonyInstance HarmonyInstance = HarmonyInstance.Create($"SMAPI.{nameof(HarmonySummaryCommand)}");
#endif
/********* /*********
** Accessors ** Accessors
*********/ *********/
@ -60,9 +48,7 @@ namespace StardewModdingAPI.Framework.Commands
{ {
PatchType.Prefix => 0, PatchType.Prefix => 0,
PatchType.Postfix => 1, PatchType.Postfix => 1,
#if HARMONY_2
PatchType.Finalizer => 2, PatchType.Finalizer => 2,
#endif
PatchType.Transpiler => 3, PatchType.Transpiler => 3,
_ => 4 _ => 4
}); });
@ -111,26 +97,16 @@ namespace StardewModdingAPI.Framework.Commands
/// <summary>Get all current Harmony patches.</summary> /// <summary>Get all current Harmony patches.</summary>
private IEnumerable<SearchResult> GetAllPatches() private IEnumerable<SearchResult> GetAllPatches()
{ {
#if HARMONY_2
foreach (MethodBase method in Harmony.GetAllPatchedMethods().ToArray()) foreach (MethodBase method in Harmony.GetAllPatchedMethods().ToArray())
#else
foreach (MethodBase method in this.HarmonyInstance.GetPatchedMethods().ToArray())
#endif
{ {
// get metadata for method // get metadata for method
#if HARMONY_2
HarmonyLib.Patches patchInfo = Harmony.GetPatchInfo(method); HarmonyLib.Patches patchInfo = Harmony.GetPatchInfo(method);
#else
Harmony.Patches patchInfo = this.HarmonyInstance.GetPatchInfo(method);
#endif
IDictionary<PatchType, IReadOnlyCollection<Patch>> patchGroups = new Dictionary<PatchType, IReadOnlyCollection<Patch>> IDictionary<PatchType, IReadOnlyCollection<Patch>> patchGroups = new Dictionary<PatchType, IReadOnlyCollection<Patch>>
{ {
[PatchType.Prefix] = patchInfo.Prefixes, [PatchType.Prefix] = patchInfo.Prefixes,
[PatchType.Postfix] = patchInfo.Postfixes, [PatchType.Postfix] = patchInfo.Postfixes,
#if HARMONY_2
[PatchType.Finalizer] = patchInfo.Finalizers, [PatchType.Finalizer] = patchInfo.Finalizers,
#endif
[PatchType.Transpiler] = patchInfo.Transpilers [PatchType.Transpiler] = patchInfo.Transpilers
}; };
@ -160,10 +136,8 @@ namespace StardewModdingAPI.Framework.Commands
/// <summary>A postfix patch.</summary> /// <summary>A postfix patch.</summary>
Postfix, Postfix,
#if HARMONY_2
/// <summary>A finalizer patch.</summary> /// <summary>A finalizer patch.</summary>
Finalizer, Finalizer,
#endif
/// <summary>A transpiler patch.</summary> /// <summary>A transpiler patch.</summary>
Transpiler Transpiler

View File

@ -1,4 +1,3 @@
#if HARMONY_2
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
@ -41,4 +40,3 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
} }
} }
} }
#endif

View File

@ -1,4 +1,3 @@
#if HARMONY_2
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
@ -81,4 +80,3 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
} }
} }
} }
#endif

View File

@ -1,4 +1,3 @@
#if HARMONY_2
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection; using System.Reflection;
@ -44,4 +43,3 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
} }
} }
} }
#endif

View File

@ -1,4 +1,3 @@
#if HARMONY_2
extern alias MonoCecilPackage; extern alias MonoCecilPackage;
using System; using System;
@ -44,7 +43,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith) public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction)
{ {
// rewrite Harmony 1.x methods to Harmony 2.0 // rewrite Harmony 1.x methods to Harmony 2.0
MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
@ -119,4 +118,3 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
} }
} }
} }
#endif

View File

@ -1,9 +1,5 @@
using System; using System;
#if HARMONY_2
using HarmonyLib; using HarmonyLib;
#else
using Harmony;
#endif
namespace StardewModdingAPI.Framework.Patching namespace StardewModdingAPI.Framework.Patching
{ {
@ -31,11 +27,7 @@ namespace StardewModdingAPI.Framework.Patching
/// <param name="patches">The patches to apply.</param> /// <param name="patches">The patches to apply.</param>
public void Apply(params IHarmonyPatch[] patches) public void Apply(params IHarmonyPatch[] patches)
{ {
#if HARMONY_2
Harmony harmony = new Harmony("SMAPI"); Harmony harmony = new Harmony("SMAPI");
#else
HarmonyInstance harmony = HarmonyInstance.Create("SMAPI");
#endif
foreach (IHarmonyPatch patch in patches) foreach (IHarmonyPatch patch in patches)
{ {
try try

View File

@ -1,8 +1,4 @@
#if HARMONY_2
using HarmonyLib; using HarmonyLib;
#else
using Harmony;
#endif
namespace StardewModdingAPI.Framework.Patching namespace StardewModdingAPI.Framework.Patching
{ {
@ -14,10 +10,6 @@ namespace StardewModdingAPI.Framework.Patching
*********/ *********/
/// <summary>Apply the Harmony patch.</summary> /// <summary>Apply the Harmony patch.</summary>
/// <param name="harmony">The Harmony instance.</param> /// <param name="harmony">The Harmony instance.</param>
#if HARMONY_2
void Apply(Harmony harmony); void Apply(Harmony harmony);
#else
void Apply(HarmonyInstance harmony);
#endif
} }
} }

View File

@ -1,36 +0,0 @@
#if !HARMONY_2
using System;
using System.Collections.Generic;
namespace StardewModdingAPI.Framework.Patching
{
/// <summary>Provides generic methods for implementing Harmony patches.</summary>
internal class PatchHelper
{
/*********
** Fields
*********/
/// <summary>The interception keys currently being intercepted.</summary>
private static readonly HashSet<string> InterceptingKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
/*********
** Public methods
*********/
/// <summary>Track a method that will be intercepted.</summary>
/// <param name="key">The intercept key.</param>
/// <returns>Returns false if the method was already marked for interception, else true.</returns>
public static bool StartIntercept(string key)
{
return PatchHelper.InterceptingKeys.Add(key);
}
/// <summary>Track a method as no longer being intercepted.</summary>
/// <param name="key">The intercept key.</param>
public static void StopIntercept(string key)
{
PatchHelper.InterceptingKeys.Remove(key);
}
}
}
#endif

View File

@ -46,6 +46,7 @@ using StardewModdingAPI.Toolkit.Utilities;
using StardewModdingAPI.Utilities; using StardewModdingAPI.Utilities;
using StardewValley; using StardewValley;
using xTile.Display; using xTile.Display;
using MiniMonoModHotfix = MonoMod.Utils.MiniMonoModHotfix;
using SObject = StardewValley.Object; using SObject = StardewValley.Object;
namespace StardewModdingAPI.Framework namespace StardewModdingAPI.Framework
@ -251,6 +252,7 @@ namespace StardewModdingAPI.Framework
StardewValley.GameRunner.instance = this.Game; StardewValley.GameRunner.instance = this.Game;
// apply game patches // apply game patches
MiniMonoModHotfix.Apply();
new GamePatcher(this.Monitor).Apply( new GamePatcher(this.Monitor).Apply(
new LoadContextPatch(this.Reflection, this.OnLoadStageChanged) new LoadContextPatch(this.Reflection, this.OnLoadStageChanged)
); );

View File

@ -0,0 +1,176 @@
// This temporary utility fixes an esoteric issue in XNA Framework where deserialization depends on
// the order of fields returned by Type.GetFields, but that order changes after Harmony/MonoMod use
// reflection to access the fields due to an issue in .NET Framework.
// https://twitter.com/0x0ade/status/1414992316964687873
//
// This will be removed when Harmony/MonoMod are updated to incorporate the fix.
//
// Special thanks to 0x0ade for submitting this worokaround! Copy/pasted and adapted from MonoMod.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using HarmonyLib;
// ReSharper disable once CheckNamespace -- Temporary hotfix submitted by the MonoMod author.
namespace MonoMod.Utils
{
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Temporary hotfix submitted by the MonoMod author.")]
[SuppressMessage("ReSharper", "PossibleNullReferenceException", Justification = "Temporary hotfix submitted by the MonoMod author.")]
static class MiniMonoModHotfix
{
// .NET Framework can break member ordering if using Module.Resolve* on certain members.
private static readonly object[] _NoArgs = new object[0];
private static readonly object[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null };
private static readonly Type t_RuntimeModule =
typeof(Module).Assembly
.GetType("System.Reflection.RuntimeModule");
private static readonly PropertyInfo p_RuntimeModule_RuntimeType =
typeof(Module).Assembly
.GetType("System.Reflection.RuntimeModule")
?.GetProperty("RuntimeType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly Type t_RuntimeType =
typeof(Type).Assembly
.GetType("System.RuntimeType");
private static readonly PropertyInfo p_RuntimeType_Cache =
typeof(Type).Assembly
.GetType("System.RuntimeType")
?.GetProperty("Cache", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly MethodInfo m_RuntimeTypeCache_GetFieldList =
typeof(Type).Assembly
.GetType("System.RuntimeType+RuntimeTypeCache")
?.GetMethod("GetFieldList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly MethodInfo m_RuntimeTypeCache_GetPropertyList =
typeof(Type).Assembly
.GetType("System.RuntimeType+RuntimeTypeCache")
?.GetMethod("GetPropertyList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly ConditionalWeakTable<Type, object> _CacheFixed = new ConditionalWeakTable<Type, object>();
public static void Apply()
{
var harmony = new Harmony("MiniMonoModHotfix");
harmony.Patch(
original: typeof(Harmony).Assembly
.GetType("HarmonyLib.MethodBodyReader")
.GetMethod("ReadOperand", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance),
transpiler: new HarmonyMethod(typeof(MiniMonoModHotfix), nameof(ResolveTokenFix))
);
harmony.Patch(
original: typeof(Harmony).Assembly
.GetType("MonoMod.Utils.DynamicMethodDefinition+<>c__DisplayClass3_0")
.GetMethod("<_CopyMethodToDefinition>g__ResolveTokenAs|1", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance),
transpiler: new HarmonyMethod(typeof(MiniMonoModHotfix), nameof(ResolveTokenFix))
);
}
private static IEnumerable<CodeInstruction> ResolveTokenFix(IEnumerable<CodeInstruction> instrs)
{
MethodInfo getdecl = typeof(MiniMonoModHotfix).GetMethod(nameof(GetRealDeclaringType));
MethodInfo fixup = typeof(MiniMonoModHotfix).GetMethod(nameof(FixReflectionCache));
foreach (CodeInstruction instr in instrs)
{
yield return instr;
if (instr.operand is MethodInfo called)
{
switch (called.Name)
{
case "ResolveType":
// type.FixReflectionCache();
yield return new CodeInstruction(OpCodes.Dup);
yield return new CodeInstruction(OpCodes.Call, fixup);
break;
case "ResolveMember":
case "ResolveMethod":
case "ResolveField":
// member.GetRealDeclaringType().FixReflectionCache();
yield return new CodeInstruction(OpCodes.Dup);
yield return new CodeInstruction(OpCodes.Call, getdecl);
yield return new CodeInstruction(OpCodes.Call, fixup);
break;
}
}
}
}
public static Type GetModuleType(this Module module)
{
// Sadly we can't blindly resolve type 0x02000001 as the runtime throws ArgumentException.
if (module == null || t_RuntimeModule == null || !t_RuntimeModule.IsInstanceOfType(module))
return null;
// .NET
if (p_RuntimeModule_RuntimeType != null)
return (Type)p_RuntimeModule_RuntimeType.GetValue(module, _NoArgs);
// The hotfix doesn't apply to Mono anyway, thus that's not copied over.
return null;
}
public static Type GetRealDeclaringType(this MemberInfo member)
=> member.DeclaringType ?? member.Module?.GetModuleType();
public static void FixReflectionCache(this Type type)
{
if (t_RuntimeType == null ||
p_RuntimeType_Cache == null ||
m_RuntimeTypeCache_GetFieldList == null ||
m_RuntimeTypeCache_GetPropertyList == null)
return;
for (; type != null; type = type.DeclaringType)
{
// All types SHOULD inherit RuntimeType, including those built at runtime.
// One might never know what awaits us in the depths of reflection hell though.
if (!t_RuntimeType.IsInstanceOfType(type))
continue;
_CacheFixed.GetValue(type, rt =>
{
object cache = p_RuntimeType_Cache.GetValue(rt, _NoArgs);
_FixReflectionCacheOrder<PropertyInfo>(cache, m_RuntimeTypeCache_GetPropertyList);
_FixReflectionCacheOrder<FieldInfo>(cache, m_RuntimeTypeCache_GetFieldList);
return new object();
});
}
}
private static void _FixReflectionCacheOrder<T>(object cache, MethodInfo getter) where T : MemberInfo
{
// Get and discard once, otherwise we might not be getting the actual backing array.
getter.Invoke(cache, _CacheGetterArgs);
Array orig = (Array)getter.Invoke(cache, _CacheGetterArgs);
// Sort using a short-lived list.
List<T> list = new List<T>(orig.Length);
for (int i = 0; i < orig.Length; i++)
list.Add((T)orig.GetValue(i));
list.Sort((a, b) => a.MetadataToken - b.MetadataToken);
for (int i = orig.Length - 1; i >= 0; --i)
orig.SetValue(list[i], i);
}
}
}

View File

@ -48,10 +48,8 @@ namespace StardewModdingAPI.Metadata
yield return new HeuristicFieldRewriter(this.ValidateReferencesToAssemblies); yield return new HeuristicFieldRewriter(this.ValidateReferencesToAssemblies);
yield return new HeuristicMethodRewriter(this.ValidateReferencesToAssemblies); yield return new HeuristicMethodRewriter(this.ValidateReferencesToAssemblies);
#if HARMONY_2 // rewrite for SMAPI 3.12 (Harmony 1.x => 2.0 update)
// rewrite for SMAPI 3.x (Harmony 1.x => 2.0 update)
yield return new Harmony1AssemblyRewriter(); yield return new Harmony1AssemblyRewriter();
#endif
} }
/**** /****
@ -64,11 +62,7 @@ namespace StardewModdingAPI.Metadata
/**** /****
** detect code which may impact game stability ** detect code which may impact game stability
****/ ****/
#if HARMONY_2
yield return new TypeFinder(typeof(HarmonyLib.Harmony).FullName, InstructionHandleResult.DetectedGamePatch); yield return new TypeFinder(typeof(HarmonyLib.Harmony).FullName, InstructionHandleResult.DetectedGamePatch);
#else
yield return new TypeFinder(typeof(Harmony.HarmonyInstance).FullName, InstructionHandleResult.DetectedGamePatch);
#endif
yield return new TypeFinder("System.Runtime.CompilerServices.CallSite", InstructionHandleResult.DetectedDynamic); yield return new TypeFinder("System.Runtime.CompilerServices.CallSite", InstructionHandleResult.DetectedDynamic);
yield return new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.serializer), InstructionHandleResult.DetectedSaveSerializer); yield return new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.serializer), InstructionHandleResult.DetectedSaveSerializer);
yield return new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.farmerSerializer), InstructionHandleResult.DetectedSaveSerializer); yield return new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.farmerSerializer), InstructionHandleResult.DetectedSaveSerializer);

View File

@ -1,10 +1,6 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
#if HARMONY_2
using HarmonyLib; using HarmonyLib;
#else
using Harmony;
#endif
using StardewModdingAPI.Enums; using StardewModdingAPI.Enums;
using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Patching;
using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Reflection;
@ -46,11 +42,7 @@ namespace StardewModdingAPI.Patches
} }
/// <inheritdoc /> /// <inheritdoc />
#if HARMONY_2
public void Apply(Harmony harmony) public void Apply(Harmony harmony)
#else
public void Apply(HarmonyInstance harmony)
#endif
{ {
// detect CreatedBasicInfo // detect CreatedBasicInfo
harmony.Patch( harmony.Patch(