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);