diff --git a/Il2CppInspector.Common/Plugins/API/ReentrantAttribute.cs b/Il2CppInspector.Common/Plugins/API/ReentrantAttribute.cs
new file mode 100644
index 0000000..010619d
--- /dev/null
+++ b/Il2CppInspector.Common/Plugins/API/ReentrantAttribute.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Il2CppInspector.PluginAPI
+{
+ ///
+ /// Setting this attribute on an interface method will allow the method to be called again
+ /// while it is already running, ie. it enables recursion. Disabled by default.
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public class ReentrantAttribute : Attribute { }
+}
diff --git a/Il2CppInspector.Common/Plugins/PluginHooks.cs b/Il2CppInspector.Common/Plugins/Internal/PluginHooks.cs
similarity index 95%
rename from Il2CppInspector.Common/Plugins/PluginHooks.cs
rename to Il2CppInspector.Common/Plugins/Internal/PluginHooks.cs
index 0e67868..0a3aec5 100644
--- a/Il2CppInspector.Common/Plugins/PluginHooks.cs
+++ b/Il2CppInspector.Common/Plugins/Internal/PluginHooks.cs
@@ -15,6 +15,7 @@ namespace Il2CppInspector
{
// Internal helpers to call the same hook on every plugin
// Does not include hooks that should be called individually, eg. OptionsChanged
+ // NOTE: The method names must be identical to the interface method names for stack tracing to work
internal static class PluginHooks
{
public static PluginLoadPipelineStartingEventInfo LoadPipelineStarting()
diff --git a/Il2CppInspector.Common/Plugins/PluginManager.cs b/Il2CppInspector.Common/Plugins/Internal/PluginManager.cs
similarity index 91%
rename from Il2CppInspector.Common/Plugins/PluginManager.cs
rename to Il2CppInspector.Common/Plugins/Internal/PluginManager.cs
index 8153c3b..3d426ba 100644
--- a/Il2CppInspector.Common/Plugins/PluginManager.cs
+++ b/Il2CppInspector.Common/Plugins/Internal/PluginManager.cs
@@ -11,7 +11,9 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
using McMaster.NETCore.Plugins;
+using Il2CppInspector.PluginAPI;
// This is the ONLY line to update when the API version changes
using Il2CppInspector.PluginAPI.V100;
@@ -35,6 +37,9 @@ namespace Il2CppInspector
get => Plugin.Options.Single(o => o.Name == s).Value;
set => Plugin.Options.Single(o => o.Name == s).Value = value;
}
+
+ // The current stack trace of the plugin
+ internal Stack StackTrace = new Stack();
}
// Event arguments for error handler
@@ -46,7 +51,7 @@ namespace Il2CppInspector
// The exception thrown
public Exception Exception { get; set; }
- // The name of the operation that was being performed
+ // The name of the method that was being executed
public string Operation { get; set; }
}
@@ -274,24 +279,34 @@ namespace Il2CppInspector
// Try to cast each enabled plugin to a specific interface type, and for those supporting the interface, execute the supplied delegate
// Errors will be forwarded to the error handler
- internal static E Try(Action action) where E : PluginEventInfo, new()
+ internal static E Try(Action action, [CallerMemberName] string hookName = null) where E : PluginEventInfo, new()
{
var eventInfo = new E();
+ var enabledPlugins = AsInstance.ManagedPlugins.Where(p => p.Enabled);
- foreach (var plugin in EnabledPlugins)
- if (plugin is I p)
+ foreach (var plugin in enabledPlugins)
+ if (plugin.Plugin is I p)
try {
+ // Silently disallow recursion unless [Reentrant] is set on the method
+ if (plugin.StackTrace.Contains(hookName)) {
+ var allowRecursion = p.GetType().GetMethod(hookName).GetCustomAttribute(typeof(ReentrantAttribute)) != null;
+ if (!allowRecursion)
+ continue;
+ }
+
+ plugin.StackTrace.Push(hookName);
action(p, eventInfo);
+ plugin.StackTrace.Pop();
if (eventInfo.FullyProcessed)
break;
}
catch (Exception ex) {
// Disable failing plugin
- Plugins[plugin.Id].Enabled = false;
+ plugin.Enabled = false;
// Forward error to error handler
- eventInfo.Error = new PluginErrorEventArgs { Plugin = plugin, Exception = ex, Operation = typeof(I).Name };
+ eventInfo.Error = new PluginErrorEventArgs { Plugin = plugin.Plugin, Exception = ex, Operation = hookName };
ErrorHandler?.Invoke(AsInstance, eventInfo);
}
diff --git a/Il2CppInspector.GUI/App.xaml.cs b/Il2CppInspector.GUI/App.xaml.cs
index 5035e3d..fb6f256 100644
--- a/Il2CppInspector.GUI/App.xaml.cs
+++ b/Il2CppInspector.GUI/App.xaml.cs
@@ -209,7 +209,7 @@ namespace Il2CppInspectorGUI
MessageBox.Show($"One or more plugin options for {e.Error.Plugin.Name} are invalid: {e.Error.Exception.Message}", "Plugin error");
else
MessageBox.Show($"The plugin {e.Error.Plugin.Name} encountered an error while executing {e.Error.Operation}: {e.Error.Exception.Message}."
- + Environment.NewLine + Environment.NewLine + "Plugin has been disabled.", "Plugin error");
+ + Environment.NewLine + Environment.NewLine + "Plugin has been disabled for this session.", "Plugin error");
};
PluginManager.StatusHandler += (s, e) => StatusUpdate(e.Plugin, "[" + e.Plugin.Name + "]\n" + e.Text);