diff --git a/GlobalPluginCommand.cs b/GlobalPluginCommand.cs new file mode 100644 index 0000000..fb62e33 --- /dev/null +++ b/GlobalPluginCommand.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.InteropServices; + +namespace BinaryNinja +{ + /// Hosts a global plugin command with managed callbacks. + public abstract class GlobalPluginCommand + { + private BNPluginCommand.GlobalCommandDelegate? commandCallback; + + private BNPluginCommand.GlobalIsValidDelegate? isValidCallback; + + public void Register(string name, string description = "") + { + if (null == name) + { + throw new ArgumentNullException(nameof(name)); + } + + this.commandCallback = new BNPluginCommand.GlobalCommandDelegate(this.InvokeCommand); + this.isValidCallback = new BNPluginCommand.GlobalIsValidDelegate(this.InvokeIsValid); + NativeMethods.BNRegisterPluginCommandGlobal( + name, + description ?? string.Empty, + Marshal.GetFunctionPointerForDelegate(this.commandCallback), + Marshal.GetFunctionPointerForDelegate(this.isValidCallback), + IntPtr.Zero + ); + } + + public abstract void Execute(); + + public virtual bool IsValid() { return true; } + + private void InvokeCommand(IntPtr context) { this.Execute(); } + + private bool InvokeIsValid(IntPtr context) { return this.IsValid(); } + } +} diff --git a/Struct/BNPluginCommand.cs b/Struct/BNPluginCommand.cs index e3ed9fb..dd39ffd 100644 --- a/Struct/BNPluginCommand.cs +++ b/Struct/BNPluginCommand.cs @@ -8,6 +8,13 @@ namespace BinaryNinja [StructLayout(LayoutKind.Sequential)] public unsafe struct BNPluginCommand { + [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.Cdecl)] + internal delegate void GlobalCommandDelegate(IntPtr ctxt); + + [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + internal delegate bool GlobalIsValidDelegate(IntPtr ctxt); + /// /// void (*defaultCommand)(void* ctxt, BNBinaryView* view); /// @@ -258,6 +265,8 @@ IntPtr view /// void* context /// internal IntPtr context; + + internal IntPtr globalCommand; /// /// void* defaultCommand @@ -313,6 +322,8 @@ IntPtr view /// void* projectCommand /// internal IntPtr projectCommand; + + internal IntPtr globalIsValid; /// /// void* defaultIsValid @@ -372,6 +383,10 @@ IntPtr view public sealed class PluginCommand { + public delegate void GlobalCommandDelegate(); + + public delegate bool GlobalIsValidDelegate(); + public delegate void DefaultCommandDelegate( BinaryView view ); @@ -491,6 +506,10 @@ Project project public string Description { get; private set; } = string.Empty; public PluginCommandType Type { get; private set; } = PluginCommandType.DefaultPluginCommand; + + public GlobalCommandDelegate? GlobalCommand { get; private set; } = null; + + public GlobalIsValidDelegate? GlobalIsValid { get; private set; } = null; public DefaultCommandDelegate? DefaultCommand { get; private set; } = null; @@ -537,6 +556,9 @@ Project project public ProjectIsValidDelegate? ProjectIsValid{ get; private set; } = null; #region native + private BNPluginCommand.GlobalCommandDelegate? m_globalCommand; + + private BNPluginCommand.GlobalIsValidDelegate? m_globalIsValid; /// /// void* defaultCommand @@ -664,7 +686,25 @@ internal static PluginCommand FromNative(BNPluginCommand native) Type = native.type }; - if (PluginCommandType.DefaultPluginCommand == native.type) + if (PluginCommandType.GlobalPluginCommand == native.type) + { + if (IntPtr.Zero != native.globalCommand) + { + command.m_globalCommand = Marshal.GetDelegateForFunctionPointer( + native.globalCommand + ); + command.GlobalCommand = command.GlobalCommandBridge; + } + + if (IntPtr.Zero != native.globalIsValid) + { + command.m_globalIsValid = Marshal.GetDelegateForFunctionPointer( + native.globalIsValid + ); + command.GlobalIsValid = command.GlobalIsValidBridge; + } + } + else if (PluginCommandType.DefaultPluginCommand == native.type) { if (IntPtr.Zero != native.defaultCommand) { @@ -910,6 +950,26 @@ internal static PluginCommand FromNative(BNPluginCommand native) return command; } + private void GlobalCommandBridge() + { + if (null == this.m_globalCommand) + { + throw new InvalidOperationException("The global command callback is unavailable."); + } + + this.m_globalCommand(IntPtr.Zero); + } + + private bool GlobalIsValidBridge() + { + if (null == this.m_globalIsValid) + { + return false; + } + + return this.m_globalIsValid(IntPtr.Zero); + } + private void DefaultCommandBridge( BinaryView view @@ -2820,4 +2880,4 @@ public virtual bool IsValid(Project project) return false; } } -} \ No newline at end of file +}