diff --git a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs
index 8d27f6af..6be97526 100644
--- a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs
+++ b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs
@@ -29,6 +29,12 @@ namespace SMAPI.Tests.Core
/// The random number generator with which to create sample values.
private readonly Random Random = new();
+ /// Sample user inputs for season names.
+ private static readonly IInterfaceProxyFactory[] ProxyFactories = {
+ new InterfaceProxyFactory(),
+ new OriginalInterfaceProxyFactory()
+ };
+
/*********
** Unit tests
@@ -37,8 +43,9 @@ namespace SMAPI.Tests.Core
** Events
****/
/// Assert that an event field can be proxied correctly.
+ /// The proxy factory to test.
[Test]
- public void CanProxy_EventField()
+ public void CanProxy_EventField([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
{
// arrange
ProviderMod providerMod = new();
@@ -46,7 +53,7 @@ namespace SMAPI.Tests.Core
int expectedValue = this.Random.Next();
// act
- ISimpleApi proxy = this.GetProxy(implementation);
+ ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
new ApiConsumer().UseEventField(proxy, out Func<(int timesCalled, int lastValue)> getValues);
providerMod.RaiseEvent(expectedValue);
(int timesCalled, int lastValue) = getValues();
@@ -57,8 +64,9 @@ namespace SMAPI.Tests.Core
}
/// Assert that an event property can be proxied correctly.
+ /// The proxy factory to test.
[Test]
- public void CanProxy_EventProperty()
+ public void CanProxy_EventProperty([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
{
// arrange
ProviderMod providerMod = new();
@@ -66,7 +74,7 @@ namespace SMAPI.Tests.Core
int expectedValue = this.Random.Next();
// act
- ISimpleApi proxy = this.GetProxy(implementation);
+ ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
new ApiConsumer().UseEventProperty(proxy, out Func<(int timesCalled, int lastValue)> getValues);
providerMod.RaiseEvent(expectedValue);
(int timesCalled, int lastValue) = getValues();
@@ -80,10 +88,10 @@ namespace SMAPI.Tests.Core
** Properties
****/
/// Assert that properties can be proxied correctly.
+ /// The proxy factory to test.
/// Whether to set the properties through the provider mod or proxy interface.
- [TestCase("set via provider mod")]
- [TestCase("set via proxy interface")]
- public void CanProxy_Properties(string setVia)
+ [Test]
+ public void CanProxy_Properties([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory, [Values("set via provider mod", "set via proxy interface")] string setVia)
{
// arrange
ProviderMod providerMod = new();
@@ -98,7 +106,7 @@ namespace SMAPI.Tests.Core
BindingFlags expectedEnum = BindingFlags.Instance | BindingFlags.Public;
// act
- ISimpleApi proxy = this.GetProxy(implementation);
+ ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
switch (setVia)
{
case "set via provider mod":
@@ -198,27 +206,29 @@ namespace SMAPI.Tests.Core
}
/// Assert that a simple method with no return value can be proxied correctly.
+ /// The proxy factory to test.
[Test]
- public void CanProxy_SimpleMethod_Void()
+ public void CanProxy_SimpleMethod_Void([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
{
// arrange
object implementation = new ProviderMod().GetModApi();
// act
- ISimpleApi proxy = this.GetProxy(implementation);
+ ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
proxy.GetNothing();
}
/// Assert that a simple int method can be proxied correctly.
+ /// The proxy factory to test.
[Test]
- public void CanProxy_SimpleMethod_Int()
+ public void CanProxy_SimpleMethod_Int([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
{
// arrange
object implementation = new ProviderMod().GetModApi();
int expectedValue = this.Random.Next();
// act
- ISimpleApi proxy = this.GetProxy(implementation);
+ ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
int actualValue = proxy.GetInt(expectedValue);
// assert
@@ -226,15 +236,16 @@ namespace SMAPI.Tests.Core
}
/// Assert that a simple object method can be proxied correctly.
+ /// The proxy factory to test.
[Test]
- public void CanProxy_SimpleMethod_Object()
+ public void CanProxy_SimpleMethod_Object([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
{
// arrange
object implementation = new ProviderMod().GetModApi();
object expectedValue = new();
// act
- ISimpleApi proxy = this.GetProxy(implementation);
+ ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
object actualValue = proxy.GetObject(expectedValue);
// assert
@@ -242,15 +253,16 @@ namespace SMAPI.Tests.Core
}
/// Assert that a simple list method can be proxied correctly.
+ /// The proxy factory to test.
[Test]
- public void CanProxy_SimpleMethod_List()
+ public void CanProxy_SimpleMethod_List([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
{
// arrange
object implementation = new ProviderMod().GetModApi();
string expectedValue = this.GetRandomString();
// act
- ISimpleApi proxy = this.GetProxy(implementation);
+ ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
IList actualValue = proxy.GetList(expectedValue);
// assert
@@ -258,15 +270,16 @@ namespace SMAPI.Tests.Core
}
/// Assert that a simple list with interface method can be proxied correctly.
+ /// The proxy factory to test.
[Test]
- public void CanProxy_SimpleMethod_ListWithInterface()
+ public void CanProxy_SimpleMethod_ListWithInterface([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
{
// arrange
object implementation = new ProviderMod().GetModApi();
string expectedValue = this.GetRandomString();
// act
- ISimpleApi proxy = this.GetProxy(implementation);
+ ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
IList actualValue = proxy.GetListWithInterface(expectedValue);
// assert
@@ -274,8 +287,9 @@ namespace SMAPI.Tests.Core
}
/// Assert that a simple method which returns generic types can be proxied correctly.
+ /// The proxy factory to test.
[Test]
- public void CanProxy_SimpleMethod_GenericTypes()
+ public void CanProxy_SimpleMethod_GenericTypes([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
{
// arrange
object implementation = new ProviderMod().GetModApi();
@@ -283,7 +297,7 @@ namespace SMAPI.Tests.Core
string expectedValue = this.GetRandomString();
// act
- ISimpleApi proxy = this.GetProxy(implementation);
+ ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
IDictionary> actualValue = proxy.GetGenerics(expectedKey, expectedValue);
// assert
@@ -294,16 +308,17 @@ namespace SMAPI.Tests.Core
}
/// Assert that a simple lambda method can be proxied correctly.
+ /// The proxy factory to test.
[Test]
[SuppressMessage("ReSharper", "ConvertToLocalFunction")]
- public void CanProxy_SimpleMethod_Lambda()
+ public void CanProxy_SimpleMethod_Lambda([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
{
// arrange
object implementation = new ProviderMod().GetModApi();
Func expectedValue = _ => "test";
// act
- ISimpleApi proxy = this.GetProxy(implementation);
+ ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
object actualValue = proxy.GetObject(expectedValue);
// assert
@@ -311,16 +326,17 @@ namespace SMAPI.Tests.Core
}
/// Assert that a method with out parameters can be proxied correctly.
+ /// The proxy factory to test.
[Test]
[SuppressMessage("ReSharper", "ConvertToLocalFunction")]
- public void CanProxy_Method_OutParameters()
+ public void CanProxy_Method_OutParameters([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory)
{
// arrange
object implementation = new ProviderMod().GetModApi();
const int expectedNumber = 42;
// act
- ISimpleApi proxy = this.GetProxy(implementation);
+ ISimpleApi proxy = this.GetProxy(proxyFactory, implementation);
bool result = proxy.TryGetOutParameter(
inputNumber: expectedNumber,
@@ -374,10 +390,10 @@ namespace SMAPI.Tests.Core
}
/// Get a proxy API instance.
+ /// The proxy factory to use.
/// The underlying API instance.
- private ISimpleApi GetProxy(object implementation)
+ private ISimpleApi GetProxy(IInterfaceProxyFactory proxyFactory, object implementation)
{
- var proxyFactory = new InterfaceProxyFactory();
return proxyFactory.CreateProxy(implementation, this.FromModId, this.ToModId);
}
}
diff --git a/src/SMAPI.sln.DotSettings b/src/SMAPI.sln.DotSettings
index ad546665..c8dcdb55 100644
--- a/src/SMAPI.sln.DotSettings
+++ b/src/SMAPI.sln.DotSettings
@@ -61,11 +61,13 @@
True
True
True
+ True
True
True
True
True
True
+ True
True
True
True
diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs
index 39cef758..348ba225 100644
--- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs
@@ -19,7 +19,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
private readonly HashSet AccessedModApis = new();
/// Generates proxy classes to access mod APIs through an arbitrary interface.
- private readonly InterfaceProxyFactory ProxyFactory;
+ private readonly IInterfaceProxyFactory ProxyFactory;
/*********
@@ -30,7 +30,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// The underlying mod registry.
/// Generates proxy classes to access mod APIs through an arbitrary interface.
/// Encapsulates monitoring and logging for the mod.
- public ModRegistryHelper(IModMetadata mod, ModRegistry registry, InterfaceProxyFactory proxyFactory, IMonitor monitor)
+ public ModRegistryHelper(IModMetadata mod, ModRegistry registry, IInterfaceProxyFactory proxyFactory, IMonitor monitor)
: base(mod)
{
this.Registry = registry;
diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs
index d626ab4d..1a43c1fc 100644
--- a/src/SMAPI/Framework/Models/SConfig.cs
+++ b/src/SMAPI/Framework/Models/SConfig.cs
@@ -22,7 +22,8 @@ namespace StardewModdingAPI.Framework.Models
[nameof(VerboseLogging)] = false,
[nameof(LogNetworkTraffic)] = false,
[nameof(RewriteMods)] = true,
- [nameof(AggressiveMemoryOptimizations)] = false
+ [nameof(AggressiveMemoryOptimizations)] = false,
+ [nameof(UsePintail)] = true
};
/// The default values for , to log changes if different.
@@ -64,6 +65,9 @@ namespace StardewModdingAPI.Framework.Models
/// Whether to enable more aggressive memory optimizations.
public bool AggressiveMemoryOptimizations { get; }
+ /// Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.
+ public bool UsePintail { get; }
+
/// Whether SMAPI should log network traffic. Best combined with , which includes network metadata.
public bool LogNetworkTraffic { get; }
@@ -87,10 +91,11 @@ namespace StardewModdingAPI.Framework.Models
/// Whether SMAPI should log more information about the game context.
/// Whether SMAPI should rewrite mods for compatibility.
/// Whether to enable more aggressive memory optimizations.
+ /// Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.
/// Whether SMAPI should log network traffic.
/// The colors to use for text written to the SMAPI console.
/// The mod IDs SMAPI should ignore when performing update checks or validating update keys.
- public SConfig(bool developerMode, bool checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, bool verboseLogging, bool? rewriteMods, bool? aggressiveMemoryOptimizations, bool logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks)
+ public SConfig(bool developerMode, bool checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, bool verboseLogging, bool? rewriteMods, bool? aggressiveMemoryOptimizations, bool usePintail, bool logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks)
{
this.DeveloperMode = developerMode;
this.CheckForUpdates = checkForUpdates;
@@ -101,6 +106,7 @@ namespace StardewModdingAPI.Framework.Models
this.VerboseLogging = verboseLogging;
this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(SConfig.RewriteMods)];
this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations ?? (bool)SConfig.DefaultValues[nameof(SConfig.AggressiveMemoryOptimizations)];
+ this.UsePintail = usePintail;
this.LogNetworkTraffic = logNetworkTraffic;
this.ConsoleColors = consoleColors;
this.SuppressUpdateChecks = suppressUpdateChecks ?? Array.Empty();
diff --git a/src/SMAPI/Framework/Reflection/IInterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/IInterfaceProxyFactory.cs
new file mode 100644
index 00000000..6429db58
--- /dev/null
+++ b/src/SMAPI/Framework/Reflection/IInterfaceProxyFactory.cs
@@ -0,0 +1,17 @@
+namespace StardewModdingAPI.Framework.Reflection
+{
+ /// Generates proxy classes to access mod APIs through an arbitrary interface.
+ internal interface IInterfaceProxyFactory
+ {
+ /*********
+ ** Methods
+ *********/
+ /// Create an API proxy.
+ /// The interface through which to access the API.
+ /// The API instance to access.
+ /// The unique ID of the mod consuming the API.
+ /// The unique ID of the mod providing the API.
+ TInterface CreateProxy(object instance, string sourceModID, string targetModID)
+ where TInterface : class;
+ }
+}
diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs
index 40adde8e..694c563d 100644
--- a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs
+++ b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs
@@ -4,8 +4,8 @@ using Nanoray.Pintail;
namespace StardewModdingAPI.Framework.Reflection
{
- /// Generates proxy classes to access mod APIs through an arbitrary interface.
- internal class InterfaceProxyFactory
+ ///
+ internal class InterfaceProxyFactory : IInterfaceProxyFactory
{
/*********
** Fields
@@ -28,11 +28,7 @@ namespace StardewModdingAPI.Framework.Reflection
));
}
- /// Create an API proxy.
- /// The interface through which to access the API.
- /// The API instance to access.
- /// The unique ID of the mod consuming the API.
- /// The unique ID of the mod providing the API.
+ ///
public TInterface CreateProxy(object instance, string sourceModID, string targetModID)
where TInterface : class
{
diff --git a/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs
new file mode 100644
index 00000000..9576f768
--- /dev/null
+++ b/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace StardewModdingAPI.Framework.Reflection
+{
+ /// Generates a proxy class to access a mod API through an arbitrary interface.
+ internal class OriginalInterfaceProxyBuilder
+ {
+ /*********
+ ** Fields
+ *********/
+ /// The target class type.
+ private readonly Type TargetType;
+
+ /// The generated proxy type.
+ private readonly Type ProxyType;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// Construct an instance.
+ /// The type name to generate.
+ /// The CLR module in which to create proxy classes.
+ /// The interface type to implement.
+ /// The target type.
+ public OriginalInterfaceProxyBuilder(string name, ModuleBuilder moduleBuilder, Type interfaceType, Type targetType)
+ {
+ // validate
+ if (name == null)
+ throw new ArgumentNullException(nameof(name));
+ if (targetType == null)
+ throw new ArgumentNullException(nameof(targetType));
+
+ // define proxy type
+ TypeBuilder proxyBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class);
+ proxyBuilder.AddInterfaceImplementation(interfaceType);
+
+ // create field to store target instance
+ FieldBuilder targetField = proxyBuilder.DefineField("__Target", targetType, FieldAttributes.Private);
+
+ // create constructor which accepts target instance and sets field
+ {
+ ConstructorBuilder constructor = proxyBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new[] { targetType });
+ ILGenerator il = constructor.GetILGenerator();
+
+ il.Emit(OpCodes.Ldarg_0); // this
+ // ReSharper disable once AssignNullToNotNullAttribute -- never null
+ il.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)!); // call base constructor
+ il.Emit(OpCodes.Ldarg_0); // this
+ il.Emit(OpCodes.Ldarg_1); // load argument
+ il.Emit(OpCodes.Stfld, targetField); // set field to loaded argument
+ il.Emit(OpCodes.Ret);
+ }
+
+ // proxy methods
+ foreach (MethodInfo proxyMethod in interfaceType.GetMethods())
+ {
+ var targetMethod = targetType.GetMethod(proxyMethod.Name, proxyMethod.GetParameters().Select(a => a.ParameterType).ToArray());
+ if (targetMethod == null)
+ throw new InvalidOperationException($"The {interfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API.");
+
+ this.ProxyMethod(proxyBuilder, targetMethod, targetField);
+ }
+
+ // save info
+ this.TargetType = targetType;
+ this.ProxyType = proxyBuilder.CreateType()!;
+ }
+
+ /// Create an instance of the proxy for a target instance.
+ /// The target instance.
+ public object CreateInstance(object targetInstance)
+ {
+ ConstructorInfo? constructor = this.ProxyType.GetConstructor(new[] { this.TargetType });
+ if (constructor == null)
+ throw new InvalidOperationException($"Couldn't find the constructor for generated proxy type '{this.ProxyType.Name}'."); // should never happen
+ return constructor.Invoke(new[] { targetInstance });
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// Define a method which proxies access to a method on the target.
+ /// The proxy type being generated.
+ /// The target method.
+ /// The proxy field containing the API instance.
+ private void ProxyMethod(TypeBuilder proxyBuilder, MethodInfo target, FieldBuilder instanceField)
+ {
+ Type[] argTypes = target.GetParameters().Select(a => a.ParameterType).ToArray();
+
+ // create method
+ MethodBuilder methodBuilder = proxyBuilder.DefineMethod(target.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual);
+ methodBuilder.SetParameters(argTypes);
+ methodBuilder.SetReturnType(target.ReturnType);
+
+ // create method body
+ {
+ ILGenerator il = methodBuilder.GetILGenerator();
+
+ // load target instance
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldfld, instanceField);
+
+ // invoke target method on instance
+ for (int i = 0; i < argTypes.Length; i++)
+ il.Emit(OpCodes.Ldarg, i + 1);
+ il.Emit(OpCodes.Call, target);
+
+ // return result
+ il.Emit(OpCodes.Ret);
+ }
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs
new file mode 100644
index 00000000..d6966978
--- /dev/null
+++ b/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace StardewModdingAPI.Framework.Reflection
+{
+ ///
+ internal class OriginalInterfaceProxyFactory : IInterfaceProxyFactory
+ {
+ /*********
+ ** Fields
+ *********/
+ /// The CLR module in which to create proxy classes.
+ private readonly ModuleBuilder ModuleBuilder;
+
+ /// The generated proxy types.
+ private readonly IDictionary Builders = new Dictionary();
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// Construct an instance.
+ public OriginalInterfaceProxyFactory()
+ {
+ AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run);
+ this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies");
+ }
+
+ ///
+ public TInterface CreateProxy(object instance, string sourceModID, string targetModID)
+ where TInterface : class
+ {
+ lock (this.Builders)
+ {
+ // validate
+ if (instance == null)
+ throw new InvalidOperationException("Can't proxy access to a null API.");
+ if (!typeof(TInterface).IsInterface)
+ throw new InvalidOperationException("The proxy type must be an interface, not a class.");
+
+ // get proxy type
+ Type targetType = instance.GetType();
+ string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{typeof(TInterface).FullName}>_To<{targetModID}_{targetType.FullName}>";
+ if (!this.Builders.TryGetValue(proxyTypeName, out OriginalInterfaceProxyBuilder? builder))
+ {
+ builder = new OriginalInterfaceProxyBuilder(proxyTypeName, this.ModuleBuilder, typeof(TInterface), targetType);
+ this.Builders[proxyTypeName] = builder;
+ }
+
+ // create instance
+ return (TInterface)builder.CreateInstance(instance);
+ }
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index b3c9087f..44853627 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -1560,7 +1560,9 @@ namespace StardewModdingAPI.Framework
{
// init
HashSet suppressUpdateChecks = new HashSet(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase);
- InterfaceProxyFactory proxyFactory = new();
+ IInterfaceProxyFactory proxyFactory = this.Settings.UsePintail
+ ? new InterfaceProxyFactory()
+ : new OriginalInterfaceProxyFactory();
// load mods
foreach (IModMetadata mod in mods)
@@ -1699,7 +1701,7 @@ namespace StardewModdingAPI.Framework
/// The user-facing reason phrase explaining why the mod couldn't be loaded (if applicable).
/// More detailed details about the error intended for developers (if any).
/// Returns whether the mod was successfully loaded.
- private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, InterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet suppressUpdateChecks, [NotNullWhen(false)] out ModFailReason? failReason, out string? errorReasonPhrase, out string? errorDetails)
+ private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, IInterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet suppressUpdateChecks, [NotNullWhen(false)] out ModFailReason? failReason, out string? errorReasonPhrase, out string? errorDetails)
{
errorDetails = null;
diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json
index 49056e83..065dfa8c 100644
--- a/src/SMAPI/SMAPI.config.json
+++ b/src/SMAPI/SMAPI.config.json
@@ -46,6 +46,12 @@ copy all the settings, or you may cause bugs due to overridden changes in future
*/
"AggressiveMemoryOptimizations": false,
+ /**
+ * Whether to use the experimental Pintail API proxying library, instead of the original
+ * proxying built into SMAPI itself.
+ */
+ "UsePintail": true,
+
/**
* Whether to add a section to the 'mod issues' list for mods which directly use potentially
* sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as