allow switching between Pintail & original API proxying
This commit is contained in:
parent
7dec519234
commit
0a69cb4bf7
|
@ -29,6 +29,12 @@ namespace SMAPI.Tests.Core
|
|||
/// <summary>The random number generator with which to create sample values.</summary>
|
||||
private readonly Random Random = new();
|
||||
|
||||
/// <summary>Sample user inputs for season names.</summary>
|
||||
private static readonly IInterfaceProxyFactory[] ProxyFactories = {
|
||||
new InterfaceProxyFactory(),
|
||||
new OriginalInterfaceProxyFactory()
|
||||
};
|
||||
|
||||
|
||||
/*********
|
||||
** Unit tests
|
||||
|
@ -37,8 +43,9 @@ namespace SMAPI.Tests.Core
|
|||
** Events
|
||||
****/
|
||||
/// <summary>Assert that an event field can be proxied correctly.</summary>
|
||||
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||
[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
|
|||
}
|
||||
|
||||
/// <summary>Assert that an event property can be proxied correctly.</summary>
|
||||
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||
[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
|
||||
****/
|
||||
/// <summary>Assert that properties can be proxied correctly.</summary>
|
||||
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||
/// <param name="setVia">Whether to set the properties through the <c>provider mod</c> or <c>proxy interface</c>.</param>
|
||||
[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
|
|||
}
|
||||
|
||||
/// <summary>Assert that a simple method with no return value can be proxied correctly.</summary>
|
||||
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||
[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();
|
||||
}
|
||||
|
||||
/// <summary>Assert that a simple int method can be proxied correctly.</summary>
|
||||
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||
[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
|
|||
}
|
||||
|
||||
/// <summary>Assert that a simple object method can be proxied correctly.</summary>
|
||||
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||
[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
|
|||
}
|
||||
|
||||
/// <summary>Assert that a simple list method can be proxied correctly.</summary>
|
||||
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||
[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<string> actualValue = proxy.GetList(expectedValue);
|
||||
|
||||
// assert
|
||||
|
@ -258,15 +270,16 @@ namespace SMAPI.Tests.Core
|
|||
}
|
||||
|
||||
/// <summary>Assert that a simple list with interface method can be proxied correctly.</summary>
|
||||
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||
[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<string> actualValue = proxy.GetListWithInterface(expectedValue);
|
||||
|
||||
// assert
|
||||
|
@ -274,8 +287,9 @@ namespace SMAPI.Tests.Core
|
|||
}
|
||||
|
||||
/// <summary>Assert that a simple method which returns generic types can be proxied correctly.</summary>
|
||||
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||
[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<string, IList<string>> actualValue = proxy.GetGenerics(expectedKey, expectedValue);
|
||||
|
||||
// assert
|
||||
|
@ -294,16 +308,17 @@ namespace SMAPI.Tests.Core
|
|||
}
|
||||
|
||||
/// <summary>Assert that a simple lambda method can be proxied correctly.</summary>
|
||||
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||
[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<string, string> 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
|
|||
}
|
||||
|
||||
/// <summary>Assert that a method with out parameters can be proxied correctly.</summary>
|
||||
/// <param name="proxyFactory">The proxy factory to test.</param>
|
||||
[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
|
|||
}
|
||||
|
||||
/// <summary>Get a proxy API instance.</summary>
|
||||
/// <param name="proxyFactory">The proxy factory to use.</param>
|
||||
/// <param name="implementation">The underlying API instance.</param>
|
||||
private ISimpleApi GetProxy(object implementation)
|
||||
private ISimpleApi GetProxy(IInterfaceProxyFactory proxyFactory, object implementation)
|
||||
{
|
||||
var proxyFactory = new InterfaceProxyFactory();
|
||||
return proxyFactory.CreateProxy<ISimpleApi>(implementation, this.FromModId, this.ToModId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,11 +61,13 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pastebin/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=pathfinding/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pathoschild/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pintail/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=premultiplied/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=premultiply/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Prenormalize/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Preprocesses/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=prerelease/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=proxying/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=pufferchick/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=rasterizer/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=reimplements/@EntryIndexedValue">True</s:Boolean>
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
private readonly HashSet<string> AccessedModApis = new();
|
||||
|
||||
/// <summary>Generates proxy classes to access mod APIs through an arbitrary interface.</summary>
|
||||
private readonly InterfaceProxyFactory ProxyFactory;
|
||||
private readonly IInterfaceProxyFactory ProxyFactory;
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -30,7 +30,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
|||
/// <param name="registry">The underlying mod registry.</param>
|
||||
/// <param name="proxyFactory">Generates proxy classes to access mod APIs through an arbitrary interface.</param>
|
||||
/// <param name="monitor">Encapsulates monitoring and logging for the mod.</param>
|
||||
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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
/// <summary>The default values for <see cref="SuppressUpdateChecks"/>, to log changes if different.</summary>
|
||||
|
@ -64,6 +65,9 @@ namespace StardewModdingAPI.Framework.Models
|
|||
/// <summary>Whether to enable more aggressive memory optimizations.</summary>
|
||||
public bool AggressiveMemoryOptimizations { get; }
|
||||
|
||||
/// <summary>Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.</summary>
|
||||
public bool UsePintail { get; }
|
||||
|
||||
/// <summary>Whether SMAPI should log network traffic. Best combined with <see cref="VerboseLogging"/>, which includes network metadata.</summary>
|
||||
public bool LogNetworkTraffic { get; }
|
||||
|
||||
|
@ -87,10 +91,11 @@ namespace StardewModdingAPI.Framework.Models
|
|||
/// <param name="verboseLogging">Whether SMAPI should log more information about the game context.</param>
|
||||
/// <param name="rewriteMods">Whether SMAPI should rewrite mods for compatibility.</param>
|
||||
/// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param>
|
||||
/// <param name="usePintail">Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.</param>
|
||||
/// <param name="logNetworkTraffic">Whether SMAPI should log network traffic.</param>
|
||||
/// <param name="consoleColors">The colors to use for text written to the SMAPI console.</param>
|
||||
/// <param name="suppressUpdateChecks">The mod IDs SMAPI should ignore when performing update checks or validating update keys.</param>
|
||||
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<string>();
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
namespace StardewModdingAPI.Framework.Reflection
|
||||
{
|
||||
/// <summary>Generates proxy classes to access mod APIs through an arbitrary interface.</summary>
|
||||
internal interface IInterfaceProxyFactory
|
||||
{
|
||||
/*********
|
||||
** Methods
|
||||
*********/
|
||||
/// <summary>Create an API proxy.</summary>
|
||||
/// <typeparam name="TInterface">The interface through which to access the API.</typeparam>
|
||||
/// <param name="instance">The API instance to access.</param>
|
||||
/// <param name="sourceModID">The unique ID of the mod consuming the API.</param>
|
||||
/// <param name="targetModID">The unique ID of the mod providing the API.</param>
|
||||
TInterface CreateProxy<TInterface>(object instance, string sourceModID, string targetModID)
|
||||
where TInterface : class;
|
||||
}
|
||||
}
|
|
@ -4,8 +4,8 @@ using Nanoray.Pintail;
|
|||
|
||||
namespace StardewModdingAPI.Framework.Reflection
|
||||
{
|
||||
/// <summary>Generates proxy classes to access mod APIs through an arbitrary interface.</summary>
|
||||
internal class InterfaceProxyFactory
|
||||
/// <inheritdoc />
|
||||
internal class InterfaceProxyFactory : IInterfaceProxyFactory
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
|
@ -28,11 +28,7 @@ namespace StardewModdingAPI.Framework.Reflection
|
|||
));
|
||||
}
|
||||
|
||||
/// <summary>Create an API proxy.</summary>
|
||||
/// <typeparam name="TInterface">The interface through which to access the API.</typeparam>
|
||||
/// <param name="instance">The API instance to access.</param>
|
||||
/// <param name="sourceModID">The unique ID of the mod consuming the API.</param>
|
||||
/// <param name="targetModID">The unique ID of the mod providing the API.</param>
|
||||
/// <inheritdoc />
|
||||
public TInterface CreateProxy<TInterface>(object instance, string sourceModID, string targetModID)
|
||||
where TInterface : class
|
||||
{
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Reflection
|
||||
{
|
||||
/// <summary>Generates a proxy class to access a mod API through an arbitrary interface.</summary>
|
||||
internal class OriginalInterfaceProxyBuilder
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The target class type.</summary>
|
||||
private readonly Type TargetType;
|
||||
|
||||
/// <summary>The generated proxy type.</summary>
|
||||
private readonly Type ProxyType;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="name">The type name to generate.</param>
|
||||
/// <param name="moduleBuilder">The CLR module in which to create proxy classes.</param>
|
||||
/// <param name="interfaceType">The interface type to implement.</param>
|
||||
/// <param name="targetType">The target type.</param>
|
||||
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()!;
|
||||
}
|
||||
|
||||
/// <summary>Create an instance of the proxy for a target instance.</summary>
|
||||
/// <param name="targetInstance">The target instance.</param>
|
||||
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
|
||||
*********/
|
||||
/// <summary>Define a method which proxies access to a method on the target.</summary>
|
||||
/// <param name="proxyBuilder">The proxy type being generated.</param>
|
||||
/// <param name="target">The target method.</param>
|
||||
/// <param name="instanceField">The proxy field containing the API instance.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Reflection
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal class OriginalInterfaceProxyFactory : IInterfaceProxyFactory
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The CLR module in which to create proxy classes.</summary>
|
||||
private readonly ModuleBuilder ModuleBuilder;
|
||||
|
||||
/// <summary>The generated proxy types.</summary>
|
||||
private readonly IDictionary<string, OriginalInterfaceProxyBuilder> Builders = new Dictionary<string, OriginalInterfaceProxyBuilder>();
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TInterface CreateProxy<TInterface>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1560,7 +1560,9 @@ namespace StardewModdingAPI.Framework
|
|||
{
|
||||
// init
|
||||
HashSet<string> suppressUpdateChecks = new HashSet<string>(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
|
|||
/// <param name="errorReasonPhrase">The user-facing reason phrase explaining why the mod couldn't be loaded (if applicable).</param>
|
||||
/// <param name="errorDetails">More detailed details about the error intended for developers (if any).</param>
|
||||
/// <returns>Returns whether the mod was successfully loaded.</returns>
|
||||
private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, InterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet<string> 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<string> suppressUpdateChecks, [NotNullWhen(false)] out ModFailReason? failReason, out string? errorReasonPhrase, out string? errorDetails)
|
||||
{
|
||||
errorDetails = null;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue