From d1bf3d52352df4bb720cf0fa87dcd6a64f35446a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 18 May 2020 22:44:06 -0400 Subject: move facade namespace (#711) --- .../RewriteFacades/AccessToolsMethods.cs | 32 ++++++++++++ .../RewriteFacades/HarmonyInstanceMethods.cs | 49 +++++++++++++++++ .../RewriteFacades/SpriteBatchMethods.cs | 61 ++++++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs create mode 100644 src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs create mode 100644 src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchMethods.cs (limited to 'src/SMAPI/Framework/ModLoading/RewriteFacades') diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs new file mode 100644 index 00000000..ea35fec9 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using HarmonyLib; + +namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades +{ + /// Maps Harmony 1.x methods to Harmony 2.x to avoid breaking older mods. + /// This is public to support SMAPI rewriting and should not be referenced directly by mods. + [SuppressMessage("ReSharper", "CS1591", Justification = "Documentation not needed for facade classes.")] + public class AccessToolsMethods + { + /********* + ** Public methods + *********/ + public static ConstructorInfo DeclaredConstructor(Type type, Type[] parameters = null) + { + return AccessTools.DeclaredConstructor(type, parameters, searchForStatic: true); + } + + public static ConstructorInfo Constructor(Type type, Type[] parameters = null) + { + return AccessTools.Constructor(type, parameters, searchForStatic: true); + } + + public static List GetDeclaredConstructors(Type type) + { + return AccessTools.GetDeclaredConstructors(type, searchForStatic: true); + } + } +} diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs new file mode 100644 index 00000000..78cf25f8 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Reflection.Emit; +using HarmonyLib; + +namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades +{ + /// Maps Harmony 1.x HarmonyInstance methods to Harmony 2.x's to avoid breaking older mods. + /// This is public to support SMAPI rewriting and should not be referenced directly by mods. + [SuppressMessage("ReSharper", "CS1591", Justification = "Documentation not needed for facade classes.")] + public class HarmonyInstanceMethods : Harmony + { + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The unique patch identifier. + public HarmonyInstanceMethods(string id) + : base(id) { } + + public static Harmony Create(string id) + { + return new Harmony(id); + } + + public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) + { + try + { + MethodInfo method = base.Patch(original: original, prefix: prefix, postfix: postfix, transpiler: transpiler); + return (DynamicMethod)method; + } + catch (Exception ex) + { + var patchTypes = new List(); + if (prefix != null) + patchTypes.Add("prefix"); + if (postfix != null) + patchTypes.Add("postfix"); + if (transpiler != null) + patchTypes.Add("transpiler"); + + throw new Exception($"Harmony instance {this.Id} failed applying {string.Join("/", patchTypes)} to method {original.DeclaringType?.FullName}.{original.Name}.", ex); + } + } + } +} diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchMethods.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchMethods.cs new file mode 100644 index 00000000..75bb61ef --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchMethods.cs @@ -0,0 +1,61 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +#pragma warning disable 1591 // missing documentation +namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades +{ + /// Provides method signatures that can be injected into mod code for compatibility between Linux/Mac or Windows. + /// This is public to support SMAPI rewriting and should not be referenced directly by mods. + public class SpriteBatchMethods : SpriteBatch + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public SpriteBatchMethods(GraphicsDevice graphicsDevice) : base(graphicsDevice) { } + + + /**** + ** MonoGame signatures + ****/ + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Linux/Mac.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix? matrix) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, matrix ?? Matrix.Identity); + } + + /**** + ** XNA signatures + ****/ + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin() + { + base.Begin(); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState) + { + base.Begin(sortMode, blendState); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect); + } + + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix transformMatrix) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, transformMatrix); + } + } +} -- cgit From c5c30189e43f93c3f3c66207945187a974656c9e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 20 May 2020 02:14:30 -0400 Subject: fix error-handling when patch is called with a null target method (#711) --- .../Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/SMAPI/Framework/ModLoading/RewriteFacades') diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs index 78cf25f8..17b6bcd9 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs @@ -34,6 +34,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades } catch (Exception ex) { + // get patch types var patchTypes = new List(); if (prefix != null) patchTypes.Add("prefix"); @@ -42,7 +43,12 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades if (transpiler != null) patchTypes.Add("transpiler"); - throw new Exception($"Harmony instance {this.Id} failed applying {string.Join("/", patchTypes)} to method {original.DeclaringType?.FullName}.{original.Name}.", ex); + // get original method label + string methodLabel = original != null + ? $"method {original.DeclaringType?.FullName}.{original.Name}" + : "null method"; + + throw new Exception($"Harmony instance {this.Id} failed applying {string.Join("/", patchTypes)} to {methodLabel}.", ex); } } } -- cgit From 7fdc3a2ab2361145693cfbf0957ecdb7564ffaa1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 21 May 2020 22:21:24 -0400 Subject: fix AccessTools facade constructor logic (#711) --- .../ModLoading/RewriteFacades/AccessToolsMethods.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/RewriteFacades') diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs index ea35fec9..08857129 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs @@ -16,17 +16,26 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades *********/ public static ConstructorInfo DeclaredConstructor(Type type, Type[] parameters = null) { - return AccessTools.DeclaredConstructor(type, parameters, searchForStatic: true); + // Harmony 1.x matched both static and instance constructors + return + AccessTools.DeclaredConstructor(type, parameters, searchForStatic: false) + ?? AccessTools.DeclaredConstructor(type, parameters, searchForStatic: true); } public static ConstructorInfo Constructor(Type type, Type[] parameters = null) { - return AccessTools.Constructor(type, parameters, searchForStatic: true); + // Harmony 1.x matched both static and instance constructors + return + AccessTools.Constructor(type, parameters, searchForStatic: false) + ?? AccessTools.Constructor(type, parameters, searchForStatic: true); } public static List GetDeclaredConstructors(Type type) { - return AccessTools.GetDeclaredConstructors(type, searchForStatic: true); + // Harmony 1.x matched both static and instance constructors + return + AccessTools.GetDeclaredConstructors(type, searchForStatic: false) + ?? AccessTools.GetDeclaredConstructors(type, searchForStatic: true); } } } -- cgit From 4468f390985e4cdff330147b4e6c6089aedfb48c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 21 May 2020 22:25:09 -0400 Subject: improve facade annotations --- .../Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs | 1 + .../ModLoading/RewriteFacades/HarmonyInstanceMethods.cs | 1 + .../Framework/ModLoading/RewriteFacades/SpriteBatchMethods.cs | 10 +++------- 3 files changed, 5 insertions(+), 7 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/RewriteFacades') diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs index 08857129..2f8eb5c4 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs @@ -8,6 +8,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades { /// Maps Harmony 1.x methods to Harmony 2.x to avoid breaking older mods. /// This is public to support SMAPI rewriting and should not be referenced directly by mods. + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via assembly rewriting")] [SuppressMessage("ReSharper", "CS1591", Justification = "Documentation not needed for facade classes.")] public class AccessToolsMethods { diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs index 17b6bcd9..68794f41 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs @@ -9,6 +9,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades { /// Maps Harmony 1.x HarmonyInstance methods to Harmony 2.x's to avoid breaking older mods. /// This is public to support SMAPI rewriting and should not be referenced directly by mods. + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via assembly rewriting")] [SuppressMessage("ReSharper", "CS1591", Justification = "Documentation not needed for facade classes.")] public class HarmonyInstanceMethods : Harmony { diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchMethods.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchMethods.cs index 75bb61ef..ba26b827 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchMethods.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchMethods.cs @@ -2,11 +2,13 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -#pragma warning disable 1591 // missing documentation namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades { /// Provides method signatures that can be injected into mod code for compatibility between Linux/Mac or Windows. /// This is public to support SMAPI rewriting and should not be referenced directly by mods. + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via assembly rewriting")] + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Linux/Mac.")] + [SuppressMessage("ReSharper", "CS1591", Justification = "Documentation not needed for facade classes.")] public class SpriteBatchMethods : SpriteBatch { /********* @@ -19,7 +21,6 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades /**** ** MonoGame signatures ****/ - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Linux/Mac.")] public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix? matrix) { base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, matrix ?? Matrix.Identity); @@ -28,31 +29,26 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades /**** ** XNA signatures ****/ - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] public new void Begin() { base.Begin(); } - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] public new void Begin(SpriteSortMode sortMode, BlendState blendState) { base.Begin(sortMode, blendState); } - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState) { base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState); } - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect) { base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect); } - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Windows.")] public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix transformMatrix) { base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, transformMatrix); -- cgit From f52370f6fa1fb3ab82a5c741fea2e8e5aee60223 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 21 May 2020 22:29:42 -0400 Subject: rename facade classes --- .../ModLoading/RewriteFacades/AccessToolsFacade.cs | 42 ++++++++++++++++ .../RewriteFacades/AccessToolsMethods.cs | 42 ---------------- .../RewriteFacades/HarmonyInstanceFacade.cs | 56 +++++++++++++++++++++ .../RewriteFacades/HarmonyInstanceMethods.cs | 56 --------------------- .../ModLoading/RewriteFacades/SpriteBatchFacade.cs | 57 ++++++++++++++++++++++ .../RewriteFacades/SpriteBatchMethods.cs | 57 ---------------------- .../Rewriters/Harmony1AssemblyRewriter.cs | 4 +- src/SMAPI/Metadata/InstructionMetadata.cs | 2 +- 8 files changed, 158 insertions(+), 158 deletions(-) create mode 100644 src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs delete mode 100644 src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs create mode 100644 src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs delete mode 100644 src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs create mode 100644 src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs delete mode 100644 src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchMethods.cs (limited to 'src/SMAPI/Framework/ModLoading/RewriteFacades') diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs new file mode 100644 index 00000000..8e4320b3 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using HarmonyLib; + +namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades +{ + /// Maps Harmony 1.x methods to Harmony 2.x to avoid breaking older mods. + /// This is public to support SMAPI rewriting and should not be referenced directly by mods. + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via assembly rewriting")] + [SuppressMessage("ReSharper", "CS1591", Justification = "Documentation not needed for facade classes.")] + public class AccessToolsFacade + { + /********* + ** Public methods + *********/ + public static ConstructorInfo DeclaredConstructor(Type type, Type[] parameters = null) + { + // Harmony 1.x matched both static and instance constructors + return + AccessTools.DeclaredConstructor(type, parameters, searchForStatic: false) + ?? AccessTools.DeclaredConstructor(type, parameters, searchForStatic: true); + } + + public static ConstructorInfo Constructor(Type type, Type[] parameters = null) + { + // Harmony 1.x matched both static and instance constructors + return + AccessTools.Constructor(type, parameters, searchForStatic: false) + ?? AccessTools.Constructor(type, parameters, searchForStatic: true); + } + + public static List GetDeclaredConstructors(Type type) + { + // Harmony 1.x matched both static and instance constructors + return + AccessTools.GetDeclaredConstructors(type, searchForStatic: false) + ?? AccessTools.GetDeclaredConstructors(type, searchForStatic: true); + } + } +} diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs deleted file mode 100644 index 2f8eb5c4..00000000 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using HarmonyLib; - -namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades -{ - /// Maps Harmony 1.x methods to Harmony 2.x to avoid breaking older mods. - /// This is public to support SMAPI rewriting and should not be referenced directly by mods. - [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via assembly rewriting")] - [SuppressMessage("ReSharper", "CS1591", Justification = "Documentation not needed for facade classes.")] - public class AccessToolsMethods - { - /********* - ** Public methods - *********/ - public static ConstructorInfo DeclaredConstructor(Type type, Type[] parameters = null) - { - // Harmony 1.x matched both static and instance constructors - return - AccessTools.DeclaredConstructor(type, parameters, searchForStatic: false) - ?? AccessTools.DeclaredConstructor(type, parameters, searchForStatic: true); - } - - public static ConstructorInfo Constructor(Type type, Type[] parameters = null) - { - // Harmony 1.x matched both static and instance constructors - return - AccessTools.Constructor(type, parameters, searchForStatic: false) - ?? AccessTools.Constructor(type, parameters, searchForStatic: true); - } - - public static List GetDeclaredConstructors(Type type) - { - // Harmony 1.x matched both static and instance constructors - return - AccessTools.GetDeclaredConstructors(type, searchForStatic: false) - ?? AccessTools.GetDeclaredConstructors(type, searchForStatic: true); - } - } -} diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs new file mode 100644 index 00000000..fa340781 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Reflection.Emit; +using HarmonyLib; + +namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades +{ + /// Maps Harmony 1.x HarmonyInstance methods to Harmony 2.x's to avoid breaking older mods. + /// This is public to support SMAPI rewriting and should not be referenced directly by mods. + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via assembly rewriting")] + [SuppressMessage("ReSharper", "CS1591", Justification = "Documentation not needed for facade classes.")] + public class HarmonyInstanceFacade : Harmony + { + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The unique patch identifier. + public HarmonyInstanceFacade(string id) + : base(id) { } + + public static Harmony Create(string id) + { + return new Harmony(id); + } + + public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) + { + try + { + MethodInfo method = base.Patch(original: original, prefix: prefix, postfix: postfix, transpiler: transpiler); + return (DynamicMethod)method; + } + catch (Exception ex) + { + // get patch types + var patchTypes = new List(); + if (prefix != null) + patchTypes.Add("prefix"); + if (postfix != null) + patchTypes.Add("postfix"); + if (transpiler != null) + patchTypes.Add("transpiler"); + + // get original method label + string methodLabel = original != null + ? $"method {original.DeclaringType?.FullName}.{original.Name}" + : "null method"; + + throw new Exception($"Harmony instance {this.Id} failed applying {string.Join("/", patchTypes)} to {methodLabel}.", ex); + } + } + } +} diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs deleted file mode 100644 index 68794f41..00000000 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Reflection.Emit; -using HarmonyLib; - -namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades -{ - /// Maps Harmony 1.x HarmonyInstance methods to Harmony 2.x's to avoid breaking older mods. - /// This is public to support SMAPI rewriting and should not be referenced directly by mods. - [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via assembly rewriting")] - [SuppressMessage("ReSharper", "CS1591", Justification = "Documentation not needed for facade classes.")] - public class HarmonyInstanceMethods : Harmony - { - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The unique patch identifier. - public HarmonyInstanceMethods(string id) - : base(id) { } - - public static Harmony Create(string id) - { - return new Harmony(id); - } - - public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) - { - try - { - MethodInfo method = base.Patch(original: original, prefix: prefix, postfix: postfix, transpiler: transpiler); - return (DynamicMethod)method; - } - catch (Exception ex) - { - // get patch types - var patchTypes = new List(); - if (prefix != null) - patchTypes.Add("prefix"); - if (postfix != null) - patchTypes.Add("postfix"); - if (transpiler != null) - patchTypes.Add("transpiler"); - - // get original method label - string methodLabel = original != null - ? $"method {original.DeclaringType?.FullName}.{original.Name}" - : "null method"; - - throw new Exception($"Harmony instance {this.Id} failed applying {string.Join("/", patchTypes)} to {methodLabel}.", ex); - } - } - } -} diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs new file mode 100644 index 00000000..cf71af77 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs @@ -0,0 +1,57 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades +{ + /// Provides method signatures that can be injected into mod code for compatibility between Linux/Mac or Windows. + /// This is public to support SMAPI rewriting and should not be referenced directly by mods. + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via assembly rewriting")] + [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Linux/Mac.")] + [SuppressMessage("ReSharper", "CS1591", Justification = "Documentation not needed for facade classes.")] + public class SpriteBatchFacade : SpriteBatch + { + /********* + ** Public methods + *********/ + /// Construct an instance. + public SpriteBatchFacade(GraphicsDevice graphicsDevice) : base(graphicsDevice) { } + + + /**** + ** MonoGame signatures + ****/ + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix? matrix) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, matrix ?? Matrix.Identity); + } + + /**** + ** XNA signatures + ****/ + public new void Begin() + { + base.Begin(); + } + + public new void Begin(SpriteSortMode sortMode, BlendState blendState) + { + base.Begin(sortMode, blendState); + } + + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState); + } + + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect); + } + + public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix transformMatrix) + { + base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, transformMatrix); + } + } +} diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchMethods.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchMethods.cs deleted file mode 100644 index ba26b827..00000000 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchMethods.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; - -namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades -{ - /// Provides method signatures that can be injected into mod code for compatibility between Linux/Mac or Windows. - /// This is public to support SMAPI rewriting and should not be referenced directly by mods. - [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via assembly rewriting")] - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Linux/Mac.")] - [SuppressMessage("ReSharper", "CS1591", Justification = "Documentation not needed for facade classes.")] - public class SpriteBatchMethods : SpriteBatch - { - /********* - ** Public methods - *********/ - /// Construct an instance. - public SpriteBatchMethods(GraphicsDevice graphicsDevice) : base(graphicsDevice) { } - - - /**** - ** MonoGame signatures - ****/ - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix? matrix) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, matrix ?? Matrix.Identity); - } - - /**** - ** XNA signatures - ****/ - public new void Begin() - { - base.Begin(); - } - - public new void Begin(SpriteSortMode sortMode, BlendState blendState) - { - base.Begin(sortMode, blendState); - } - - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState); - } - - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect); - } - - public new void Begin(SpriteSortMode sortMode, BlendState blendState, SamplerState samplerState, DepthStencilState depthStencilState, RasterizerState rasterizerState, Effect effect, Matrix transformMatrix) - { - base.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, transformMatrix); - } - } -} diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs index be98a666..ce6417a8 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs @@ -85,11 +85,11 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters switch (methodRef?.DeclaringType.FullName) { case "HarmonyLib.Harmony": - toType = typeof(HarmonyInstanceMethods); + toType = typeof(HarmonyInstanceFacade); break; case "HarmonyLib.AccessTools": - toType = typeof(AccessToolsMethods); + toType = typeof(AccessToolsFacade); break; default: diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index b7aad9da..89430a11 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -33,7 +33,7 @@ namespace StardewModdingAPI.Metadata ****/ // rewrite for crossplatform compatibility if (platformChanged) - yield return new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchMethods)); + yield return new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchFacade)); // rewrite for Stardew Valley 1.3 yield return new StaticFieldToConstantRewriter(typeof(Game1), "tileSize", Game1.tileSize); -- cgit From db0a46cb688ac47a06067b07dfe30bc2b65ec369 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 21 May 2020 23:29:23 -0400 Subject: rewrite HarmonyMethod to allow null (#711) --- .../ModLoading/Framework/RewriteHelper.cs | 11 +++++- .../RewriteFacades/HarmonyMethodFacade.cs | 45 ++++++++++++++++++++++ .../Rewriters/Harmony1AssemblyRewriter.cs | 4 ++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs (limited to 'src/SMAPI/Framework/ModLoading/RewriteFacades') diff --git a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs index 91c9dec3..36058b86 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs @@ -137,7 +137,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// Get whether a method definition matches the signature expected by a method reference. /// The method definition. /// The method reference. - public static bool HasMatchingSignature(MethodInfo definition, MethodReference reference) + public static bool HasMatchingSignature(MethodBase definition, MethodReference reference) { // // duplicated by HasMatchingSignature(MethodDefinition, MethodReference) below @@ -166,7 +166,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework public static bool HasMatchingSignature(MethodDefinition definition, MethodReference reference) { // - // duplicated by HasMatchingSignature(MethodInfo, MethodReference) above + // duplicated by HasMatchingSignature(MethodBase, MethodReference) above // // same name @@ -191,6 +191,13 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// The method reference. public static bool HasMatchingSignature(Type type, MethodReference reference) { + if (reference.Name == ".ctor") + { + return type + .GetConstructors(BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public) + .Any(method => RewriteHelper.HasMatchingSignature(method, reference)); + } + return type .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public) .Any(method => RewriteHelper.HasMatchingSignature(method, reference)); diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs new file mode 100644 index 00000000..44c97401 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs @@ -0,0 +1,45 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using HarmonyLib; + +namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades +{ + /// Maps Harmony 1.x methods to Harmony 2.x to avoid breaking older mods. + /// This is public to support SMAPI rewriting and should not be referenced directly by mods. + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via assembly rewriting")] + [SuppressMessage("ReSharper", "CS1591", Justification = "Documentation not needed for facade classes.")] + public class HarmonyMethodFacade : HarmonyMethod + { + /********* + ** Public methods + *********/ + public HarmonyMethodFacade(MethodInfo method) + { + this.ImportMethodImpl(method); + } + + public HarmonyMethodFacade(Type type, string name, Type[] parameters = null) + { + this.ImportMethodImpl(AccessTools.Method(type, name, parameters)); + } + + + /********* + ** Private methods + *********/ + /// Import a method directly using the internal HarmonyMethod code. + /// The method to import. + private void ImportMethodImpl(MethodInfo methodInfo) + { + // A null method is no longer allowed in the constructor with Harmony 2.0, but the + // internal code still handles null fine. For backwards compatibility, this bypasses + // the new restriction when the mod hasn't been updated for Harmony 2.0 yet. + + MethodInfo importMethod = typeof(HarmonyMethod).GetMethod("ImportMethod", BindingFlags.Instance | BindingFlags.NonPublic); + if (importMethod == null) + throw new InvalidOperationException("Can't find 'HarmonyMethod.ImportMethod' method"); + importMethod.Invoke(this, new object[] { methodInfo }); + } + } +} diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs index ce6417a8..8fed170a 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs @@ -92,6 +92,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters toType = typeof(AccessToolsFacade); break; + case "HarmonyLib.HarmonyMethod": + toType = typeof(HarmonyMethodFacade); + break; + default: return false; } -- cgit From 33da29b3e56a56c29ed6f36196d00881fa2aecfe Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 21 May 2020 23:50:34 -0400 Subject: rewrite Harmony.Patch method to allow non-implemented virtual methods (#711) --- .../RewriteFacades/HarmonyInstanceFacade.cs | 56 ++++++++++++++++------ 1 file changed, 41 insertions(+), 15 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/RewriteFacades') diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs index fa340781..54b91679 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs @@ -28,6 +28,13 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) { + // In Harmony 1.x you could target a virtual method that's not implemented by the + // target type, but in Harmony 2.0 you need to target the concrete implementation. + // This just resolves the method to the concrete implementation if needed. + if (original != null) + original = original.GetDeclaredMember(); + + // call Harmony 2.0 and show a detailed exception if it fails try { MethodInfo method = base.Patch(original: original, prefix: prefix, postfix: postfix, transpiler: transpiler); @@ -35,22 +42,41 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades } catch (Exception ex) { - // get patch types - var patchTypes = new List(); - if (prefix != null) - patchTypes.Add("prefix"); - if (postfix != null) - patchTypes.Add("postfix"); - if (transpiler != null) - patchTypes.Add("transpiler"); - - // get original method label - string methodLabel = original != null - ? $"method {original.DeclaringType?.FullName}.{original.Name}" - : "null method"; - - throw new Exception($"Harmony instance {this.Id} failed applying {string.Join("/", patchTypes)} to {methodLabel}.", ex); + string patchTypes = this.GetPatchTypesLabel(prefix, postfix, transpiler); + string methodLabel = this.GetMethodLabel(original); + throw new Exception($"Harmony instance {this.Id} failed applying {patchTypes} to {methodLabel}.", ex); } } + + + /********* + ** Private methods + *********/ + /// Get a human-readable label for the patch types being applies. + /// The prefix method, if any. + /// The postfix method, if any. + /// The transpiler method, if any. + private string GetPatchTypesLabel(HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) + { + var patchTypes = new List(); + + if (prefix != null) + patchTypes.Add("prefix"); + if (postfix != null) + patchTypes.Add("postfix"); + if (transpiler != null) + patchTypes.Add("transpiler"); + + return string.Join("/", patchTypes); + } + + /// Get a human-readable label for the method being patched. + /// The method being patched. + private string GetMethodLabel(MethodBase method) + { + return method != null + ? $"method {method.DeclaringType?.FullName}.{method.Name}" + : "null method"; + } } } -- cgit From dcd2c647a2abd836e8ee20f8ddad6568c9b4fbf2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 15 Jun 2020 22:17:32 -0400 Subject: temporarily restore Harmony 1.x support with compile flag (#711) --- docs/release-notes.md | 10 ++- docs/technical/smapi.md | 1 + .../Framework/Commands/HarmonySummaryCommand.cs | 2 + .../ModLoading/RewriteFacades/AccessToolsFacade.cs | 2 + .../RewriteFacades/HarmonyInstanceFacade.cs | 2 + .../RewriteFacades/HarmonyMethodFacade.cs | 2 + .../Rewriters/Harmony1AssemblyRewriter.cs | 2 + src/SMAPI/Framework/Patching/GamePatcher.cs | 8 ++ src/SMAPI/Framework/Patching/IHarmonyPatch.cs | 8 ++ src/SMAPI/Framework/Patching/PatchHelper.cs | 36 +++++++++ src/SMAPI/Framework/SCore.cs | 2 + src/SMAPI/Metadata/InstructionMetadata.cs | 6 ++ src/SMAPI/Patches/DialogueErrorPatch.cs | 94 +++++++++++++++++++++- src/SMAPI/Patches/EventErrorPatch.cs | 48 ++++++++++- src/SMAPI/Patches/LoadContextPatch.cs | 8 ++ src/SMAPI/Patches/LoadErrorPatch.cs | 8 ++ src/SMAPI/Patches/ObjectErrorPatch.cs | 50 +++++++++++- src/SMAPI/Patches/ScheduleErrorPatch.cs | 50 +++++++++++- src/SMAPI/SMAPI.csproj | 2 +- 19 files changed, 328 insertions(+), 13 deletions(-) create mode 100644 src/SMAPI/Framework/Patching/PatchHelper.cs (limited to 'src/SMAPI/Framework/ModLoading/RewriteFacades') diff --git a/docs/release-notes.md b/docs/release-notes.md index dd87c1fc..c47ee835 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,7 +1,12 @@ ← [README](README.md) # Release notes -## Upcoming released +## Upcoming release + 1 +* For modders: + * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). + * Added `harmony_summary` console command which lists all current Harmony patches, optionally with a search filter. + +## Upcoming release * For players: * Mod warnings are now listed alphabetically. * MacOS files starting with `._` are now ignored and can no longer cause skipped mods. @@ -17,12 +22,10 @@ * Internal changes to improve performance and reliability. * For modders: - * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). * Added [event priorities](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Custom_priority) (thanks to spacechase0!). * Added [update subkeys](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Update_checks#Update_subkeys). * Added `Multiplayer.PeerConnected` event. * Added ability to override update keys from the compatibility list. - * Added `harmony_summary` console command which lists all current Harmony patches, optionally with a search filter. * Harmony mods which use the `[HarmonyPatch(type)]` attribute now work crossplatform. Previously SMAPI couldn't rewrite types in custom attributes for compatibility. * Improved mod rewriting for compatibility: * Fixed rewriting types in custom attributes. @@ -33,6 +36,7 @@ * Fixed `ModMessageReceived` event handlers not tracked for performance monitoring. * For SMAPI developers: + * Added support for bundling a custom Harmony build for upcoming use. * Eliminated MongoDB storage in the web services, which complicated the code unnecessarily. The app still uses an abstract interface for storage, so we can wrap a distributed cache in the future if needed. * Overhauled update checks to simplify individual clients, centralize common logic, and enable upcoming features. * Merged the separate legacy redirects app on AWS into the main app on Azure. diff --git a/docs/technical/smapi.md b/docs/technical/smapi.md index ca8a9c70..3b2d6e56 100644 --- a/docs/technical/smapi.md +++ b/docs/technical/smapi.md @@ -58,6 +58,7 @@ SMAPI uses a small number of conditional compilation constants, which you can se flag | purpose ---- | ------- `SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`. +`HARMONY_2` | Whether to enable experimental Harmony 2.0 support. Existing Harmony 1._x_ mods will be rewritten automatically for compatibility. ## For SMAPI developers ### Compiling from source diff --git a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs index 08233feb..8c20fbdd 100644 --- a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs +++ b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs @@ -1,3 +1,4 @@ +#if HARMONY_2 using System; using System.Collections.Generic; using System.Linq; @@ -166,3 +167,4 @@ namespace StardewModdingAPI.Framework.Commands } } } +#endif diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs index 8e4320b3..102f3364 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs @@ -1,3 +1,4 @@ +#if HARMONY_2 using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -40,3 +41,4 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades } } } +#endif diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs index 54b91679..ad6d5e4f 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs @@ -1,3 +1,4 @@ +#if HARMONY_2 using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -80,3 +81,4 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades } } } +#endif diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs index 44c97401..f3975558 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs @@ -1,3 +1,4 @@ +#if HARMONY_2 using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; @@ -43,3 +44,4 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades } } } +#endif diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs index 8fed170a..b30d686e 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs @@ -1,3 +1,4 @@ +#if HARMONY_2 using System; using HarmonyLib; using Mono.Cecil; @@ -125,3 +126,4 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters } } } +#endif diff --git a/src/SMAPI/Framework/Patching/GamePatcher.cs b/src/SMAPI/Framework/Patching/GamePatcher.cs index dcad285a..82d7b9c8 100644 --- a/src/SMAPI/Framework/Patching/GamePatcher.cs +++ b/src/SMAPI/Framework/Patching/GamePatcher.cs @@ -1,5 +1,9 @@ using System; +#if HARMONY_2 using HarmonyLib; +#else +using Harmony; +#endif namespace StardewModdingAPI.Framework.Patching { @@ -27,7 +31,11 @@ namespace StardewModdingAPI.Framework.Patching /// The patches to apply. public void Apply(params IHarmonyPatch[] patches) { +#if HARMONY_2 Harmony harmony = new Harmony("SMAPI"); +#else + HarmonyInstance harmony = HarmonyInstance.Create("SMAPI"); +#endif foreach (IHarmonyPatch patch in patches) { try diff --git a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs b/src/SMAPI/Framework/Patching/IHarmonyPatch.cs index 7d5eb3d4..922243fa 100644 --- a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs +++ b/src/SMAPI/Framework/Patching/IHarmonyPatch.cs @@ -1,4 +1,8 @@ +#if HARMONY_2 using HarmonyLib; +#else +using Harmony; +#endif namespace StardewModdingAPI.Framework.Patching { @@ -10,6 +14,10 @@ namespace StardewModdingAPI.Framework.Patching /// Apply the Harmony patch. /// The Harmony instance. +#if HARMONY_2 void Apply(Harmony harmony); +#else + void Apply(HarmonyInstance harmony); +#endif } } diff --git a/src/SMAPI/Framework/Patching/PatchHelper.cs b/src/SMAPI/Framework/Patching/PatchHelper.cs new file mode 100644 index 00000000..d1aa0185 --- /dev/null +++ b/src/SMAPI/Framework/Patching/PatchHelper.cs @@ -0,0 +1,36 @@ +#if !HARMONY_2 +using System; +using System.Collections.Generic; + +namespace StardewModdingAPI.Framework.Patching +{ + /// Provides generic methods for implementing Harmony patches. + internal class PatchHelper + { + /********* + ** Fields + *********/ + /// The interception keys currently being intercepted. + private static readonly HashSet InterceptingKeys = new HashSet(StringComparer.OrdinalIgnoreCase); + + + /********* + ** Public methods + *********/ + /// Track a method that will be intercepted. + /// The intercept key. + /// Returns false if the method was already marked for interception, else true. + public static bool StartIntercept(string key) + { + return PatchHelper.InterceptingKeys.Add(key); + } + + /// Track a method as no longer being intercepted. + /// The intercept key. + public static void StopIntercept(string key) + { + PatchHelper.InterceptingKeys.Remove(key); + } + } +} +#endif diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 530b6754..1a2c97f4 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -511,7 +511,9 @@ namespace StardewModdingAPI.Framework this.Monitor.Log("Type 'help' for help, or 'help ' for a command's usage", LogLevel.Info); this.GameInstance.CommandManager .Add(new HelpCommand(this.GameInstance.CommandManager), this.Monitor) +#if HARMONY_2 .Add(new HarmonySummaryCommand(), this.Monitor) +#endif .Add(new ReloadI18nCommand(this.ReloadTranslations), this.Monitor); // start handling command line input diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index 89430a11..79d7a7a8 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -38,8 +38,10 @@ namespace StardewModdingAPI.Metadata // rewrite for Stardew Valley 1.3 yield return new StaticFieldToConstantRewriter(typeof(Game1), "tileSize", Game1.tileSize); +#if HARMONY_2 // rewrite for SMAPI 3.6 (Harmony 1.x => 2.0 update) yield return new Harmony1AssemblyRewriter(); +#endif /**** ** detect mod issues @@ -51,7 +53,11 @@ namespace StardewModdingAPI.Metadata /**** ** detect code which may impact game stability ****/ +#if HARMONY_2 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 FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.serializer), InstructionHandleResult.DetectedSaveSerializer); yield return new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.farmerSerializer), InstructionHandleResult.DetectedSaveSerializer); diff --git a/src/SMAPI/Patches/DialogueErrorPatch.cs b/src/SMAPI/Patches/DialogueErrorPatch.cs index cddf29d6..8043eda3 100644 --- a/src/SMAPI/Patches/DialogueErrorPatch.cs +++ b/src/SMAPI/Patches/DialogueErrorPatch.cs @@ -1,11 +1,16 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using HarmonyLib; -using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Reflection; using StardewValley; +#if HARMONY_2 +using HarmonyLib; +using StardewModdingAPI.Framework; +#else +using System.Reflection; +using Harmony; +#endif namespace StardewModdingAPI.Patches { @@ -47,6 +52,7 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. +#if HARMONY_2 public void Apply(Harmony harmony) { harmony.Patch( @@ -58,11 +64,24 @@ namespace StardewModdingAPI.Patches 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 *********/ +#if HARMONY_2 /// The method to call after the Dialogue constructor. /// The instance being patched. /// The dialogue being parsed. @@ -102,5 +121,74 @@ namespace StardewModdingAPI.Patches return null; } +#else + + /// The method to call instead of the Dialogue constructor. + /// The instance being patched. + /// The dialogue being parsed. + /// The NPC for which the dialogue is being parsed. + /// Returns whether to execute the original method. + private static bool Before_Dialogue_Constructor(Dialogue __instance, string masterDialogue, NPC speaker) + { + // get private members + bool nameArraysTranslated = DialogueErrorPatch.Reflection.GetField(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"); + IReflectedField> dialogues = DialogueErrorPatch.Reflection.GetField>(__instance, "dialogues"); + + // replicate base constructor + if (dialogues.GetValue() == null) + dialogues.SetValue(new List()); + + // 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; + } + + /// The method to call instead of . + /// The instance being patched. + /// The return value of the original method. + /// The method being wrapped. + /// Returns whether to execute the original method. + private static bool Before_NPC_CurrentDialogue(NPC __instance, ref Stack __result, MethodInfo __originalMethod) + { + const string key = nameof(Before_NPC_CurrentDialogue); + if (!PatchHelper.StartIntercept(key)) + return true; + + try + { + __result = (Stack)__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(); + return false; + } + finally + { + PatchHelper.StopIntercept(key); + } + } +#endif } } diff --git a/src/SMAPI/Patches/EventErrorPatch.cs b/src/SMAPI/Patches/EventErrorPatch.cs index de9dea29..4dbb25f3 100644 --- a/src/SMAPI/Patches/EventErrorPatch.cs +++ b/src/SMAPI/Patches/EventErrorPatch.cs @@ -1,6 +1,11 @@ -using System; using System.Diagnostics.CodeAnalysis; +#if HARMONY_2 +using System; using HarmonyLib; +#else +using System.Reflection; +using Harmony; +#endif using StardewModdingAPI.Framework.Patching; using StardewValley; @@ -38,6 +43,7 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. +#if HARMONY_2 public void Apply(Harmony harmony) { harmony.Patch( @@ -45,11 +51,21 @@ namespace StardewModdingAPI.Patches finalizer: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Finalize_GameLocation_CheckEventPrecondition)) ); } +#else + public void Apply(HarmonyInstance harmony) + { + harmony.Patch( + original: AccessTools.Method(typeof(GameLocation), "checkEventPrecondition"), + prefix: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Before_GameLocation_CheckEventPrecondition)) + ); + } +#endif /********* ** Private methods *********/ +#if HARMONY_2 /// The method to call instead of the GameLocation.CheckEventPrecondition. /// The return value of the original method. /// The precondition to be parsed. @@ -65,5 +81,35 @@ namespace StardewModdingAPI.Patches return null; } +#else + /// The method to call instead of the GameLocation.CheckEventPrecondition. + /// The instance being patched. + /// The return value of the original method. + /// The precondition to be parsed. + /// The method being wrapped. + /// Returns whether to execute the original method. + private static bool Before_GameLocation_CheckEventPrecondition(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod) + { + const string key = nameof(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; + EventErrorPatch.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{ex.InnerException}", LogLevel.Error); + return false; + } + finally + { + PatchHelper.StopIntercept(key); + } + } +#endif } } diff --git a/src/SMAPI/Patches/LoadContextPatch.cs b/src/SMAPI/Patches/LoadContextPatch.cs index 9c707676..768ddd6b 100644 --- a/src/SMAPI/Patches/LoadContextPatch.cs +++ b/src/SMAPI/Patches/LoadContextPatch.cs @@ -1,6 +1,10 @@ using System; using System.Diagnostics.CodeAnalysis; +#if HARMONY_2 using HarmonyLib; +#else +using Harmony; +#endif using StardewModdingAPI.Enums; using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Reflection; @@ -47,7 +51,11 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. +#if HARMONY_2 public void Apply(Harmony harmony) +#else + public void Apply(HarmonyInstance harmony) +#endif { // detect CreatedBasicInfo harmony.Patch( diff --git a/src/SMAPI/Patches/LoadErrorPatch.cs b/src/SMAPI/Patches/LoadErrorPatch.cs index f8ad6693..5e67b169 100644 --- a/src/SMAPI/Patches/LoadErrorPatch.cs +++ b/src/SMAPI/Patches/LoadErrorPatch.cs @@ -2,7 +2,11 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +#if HARMONY_2 using HarmonyLib; +#else +using Harmony; +#endif using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Patching; using StardewValley; @@ -49,7 +53,11 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. +#if HARMONY_2 public void Apply(Harmony harmony) +#else + public void Apply(HarmonyInstance harmony) +#endif { harmony.Patch( original: AccessTools.Method(typeof(SaveGame), nameof(SaveGame.loadDataToLocations)), diff --git a/src/SMAPI/Patches/ObjectErrorPatch.cs b/src/SMAPI/Patches/ObjectErrorPatch.cs index 189a14a0..4edcc64e 100644 --- a/src/SMAPI/Patches/ObjectErrorPatch.cs +++ b/src/SMAPI/Patches/ObjectErrorPatch.cs @@ -1,11 +1,16 @@ -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using HarmonyLib; using StardewModdingAPI.Framework.Patching; using StardewValley; using StardewValley.Menus; using SObject = StardewValley.Object; +#if HARMONY_2 +using System; +using HarmonyLib; +#else +using System.Reflection; +using Harmony; +#endif namespace StardewModdingAPI.Patches { @@ -27,7 +32,11 @@ namespace StardewModdingAPI.Patches *********/ /// Apply the Harmony patch. /// The Harmony instance. +#if HARMONY_2 public void Apply(Harmony harmony) +#else + public void Apply(HarmonyInstance harmony) +#endif { // object.getDescription harmony.Patch( @@ -38,7 +47,11 @@ namespace StardewModdingAPI.Patches // object.getDisplayName harmony.Patch( original: AccessTools.Method(typeof(SObject), "loadDisplayName"), +#if HARMONY_2 finalizer: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Finalize_Object_loadDisplayName)) +#else + prefix: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Before_Object_loadDisplayName)) +#endif ); // IClickableMenu.drawToolTip @@ -68,6 +81,7 @@ namespace StardewModdingAPI.Patches return true; } +#if HARMONY_2 /// The method to call after . /// The patched method's return value. /// The exception thrown by the wrapped method, if any. @@ -82,6 +96,38 @@ namespace StardewModdingAPI.Patches return __exception; } +#else + /// The method to call instead of . + /// The instance being patched. + /// The patched method's return value. + /// The method being wrapped. + /// Returns whether to execute the original method. + private static bool Before_Object_loadDisplayName(SObject __instance, ref string __result, MethodInfo __originalMethod) + { + const string key = nameof(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 /// The method to call instead of . /// The item for which to draw a tooltip. diff --git a/src/SMAPI/Patches/ScheduleErrorPatch.cs b/src/SMAPI/Patches/ScheduleErrorPatch.cs index df6ffab3..cc2238b0 100644 --- a/src/SMAPI/Patches/ScheduleErrorPatch.cs +++ b/src/SMAPI/Patches/ScheduleErrorPatch.cs @@ -1,10 +1,15 @@ -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using HarmonyLib; -using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.Patching; using StardewValley; +#if HARMONY_2 +using System; +using HarmonyLib; +using StardewModdingAPI.Framework; +#else +using System.Reflection; +using Harmony; +#endif namespace StardewModdingAPI.Patches { @@ -40,11 +45,19 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. +#if HARMONY_2 public void Apply(Harmony harmony) +#else + public void Apply(HarmonyInstance harmony) +#endif { harmony.Patch( original: AccessTools.Method(typeof(NPC), "parseMasterSchedule"), +#if HARMONY_2 finalizer: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Finalize_NPC_parseMasterSchedule)) +#else + prefix: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Before_NPC_parseMasterSchedule)) +#endif ); } @@ -52,6 +65,7 @@ namespace StardewModdingAPI.Patches /********* ** Private methods *********/ +#if HARMONY_2 /// The method to call instead of . /// The raw schedule data to parse. /// The instance being patched. @@ -68,5 +82,35 @@ namespace StardewModdingAPI.Patches return null; } +#else + /// The method to call instead of . + /// The raw schedule data to parse. + /// The instance being patched. + /// The patched method's return value. + /// The method being wrapped. + /// Returns whether to execute the original method. + private static bool Before_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary __result, MethodInfo __originalMethod) + { + const string key = nameof(Before_NPC_parseMasterSchedule); + if (!PatchHelper.StartIntercept(key)) + return true; + + try + { + __result = (Dictionary)__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(); + return false; + } + finally + { + PatchHelper.StopIntercept(key); + } + } +#endif } } diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 603b6fb5..c17de6d0 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -14,7 +14,7 @@ - + -- cgit