From 48536ee89de6c86cda670c477d6a91cbb3f77659 Mon Sep 17 00:00:00 2001 From: Zion Leonahenahe Basque Date: Thu, 7 Nov 2024 12:31:52 -0700 Subject: [PATCH] Feat: Add an API for start callbacks (#134) --- .../bs_change_watcher/__init__.py | 6 +++-- libbs/__init__.py | 2 +- libbs/api/decompiler_interface.py | 25 ++++++++++++++++--- libbs/decompilers/ida/compat.py | 1 + libbs/decompilers/ida/interface.py | 2 +- 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/examples/change_watcher_plugin/bs_change_watcher/__init__.py b/examples/change_watcher_plugin/bs_change_watcher/__init__.py index 3598a00..2472073 100644 --- a/examples/change_watcher_plugin/bs_change_watcher/__init__.py +++ b/examples/change_watcher_plugin/bs_change_watcher/__init__.py @@ -17,11 +17,13 @@ def create_plugin(*args, **kwargs): FunctionHeader, StackVariable, Enum, Struct, GlobalVariable, Comment, Context ) - decompiler_started_event_callbacks = [lambda *x, **y: print(f"[BSChangeWatcher] Started with plugin version {__version__}")] + decompiler_opened_callbacks = [lambda *x, **y: print(f"[BSChangeWatcher] Started with plugin version {__version__}")] + decompiler_closed_callbacks = [lambda *x, **y: print(f"[BSChangeWatcher] Goodbye!")] deci = DecompilerInterface.discover( plugin_name="ArtifactChangeWatcher", init_plugin=True, - decompiler_started_callbacks=decompiler_started_event_callbacks, + decompiler_opened_callbacks=decompiler_opened_callbacks, + decompiler_closed_callbacks=decompiler_closed_callbacks, # passing the flag below forces click recording to start on decompiler startup # force_click_recording = True, gui_init_args=args, diff --git a/libbs/__init__.py b/libbs/__init__.py index ef4f299..0fec610 100644 --- a/libbs/__init__.py +++ b/libbs/__init__.py @@ -1,4 +1,4 @@ -__version__ = "2.4.0" +__version__ = "2.5.0" import logging diff --git a/libbs/api/decompiler_interface.py b/libbs/api/decompiler_interface.py index fb93434..bc4eac0 100644 --- a/libbs/api/decompiler_interface.py +++ b/libbs/api/decompiler_interface.py @@ -62,7 +62,8 @@ def __init__( # [artifact_class] = list(callback_func) artifact_change_callbacks: Optional[Dict[Type[Artifact], List[Callable]]] = None, undo_event_callbacks: Optional[List[Callable]] = None, - decompiler_started_callbacks: Optional[List[Callable]] = None, + decompiler_opened_callbacks: Optional[List[Callable]] = None, + decompiler_closed_callbacks: Optional[List[Callable]] = None, thread_artifact_callbacks: bool = True, force_click_recording: bool = False, ): @@ -92,7 +93,8 @@ def __init__( # callback functions, keyed by Artifact class self.artifact_change_callbacks = artifact_change_callbacks or defaultdict(list) self.undo_event_callbacks = undo_event_callbacks or [] - self.decompiler_started_callbacks = decompiler_started_callbacks or [] + self.decompiler_opened_callbacks = decompiler_opened_callbacks or [] + self.decompiler_closed_callbacks = decompiler_closed_callbacks or [] self._thread_artifact_callbacks = thread_artifact_callbacks # artifact dict aliases: @@ -637,8 +639,23 @@ def _set_function_header(self, fheader: FunctionHeader, **kwargs) -> bool: # lift it ONCE inside this function. Each one will return the lifted form, for easier overriding. # - def decompiler_started_event(self, **kwargs): - for callback_func in self.decompiler_started_callbacks: + def decompiler_opened_event(self, **kwargs): + """ + This function is called when the decompiler platform this interface is running on is opened for the first time. + In the presence of a decompiler with multiple tabs, this function will still only be called once. + """ + for callback_func in self.decompiler_opened_callbacks: + if self._thread_artifact_callbacks: + threading.Thread(target=callback_func, kwargs=kwargs, daemon=True).start() + else: + callback_func(**kwargs) + + def decompiler_closed_event(self, **kwargs): + """ + This function is called when the decompiler platform this interface is running on is closing/closed. + In the presence of a decompiler with multiple tabs, this function will still only be called once. + """ + for callback_func in self.decompiler_closed_callbacks: if self._thread_artifact_callbacks: threading.Thread(target=callback_func, kwargs=kwargs, daemon=True).start() else: diff --git a/libbs/decompilers/ida/compat.py b/libbs/decompilers/ida/compat.py index cf97791..25343d5 100644 --- a/libbs/decompilers/ida/compat.py +++ b/libbs/decompilers/ida/compat.py @@ -1732,6 +1732,7 @@ def run(self, arg): pass def term(self): + self.interface.decompiler_closed_event() del self.interface cls = GenericIDAPlugin diff --git a/libbs/decompilers/ida/interface.py b/libbs/decompilers/ida/interface.py index 041e63d..ce56098 100755 --- a/libbs/decompilers/ida/interface.py +++ b/libbs/decompilers/ida/interface.py @@ -62,7 +62,7 @@ def _init_gui_hooks(self): hook.hook() def _init_gui_plugin(self, *args, **kwargs): - self.decompiler_started_event() + self.decompiler_opened_event() plugin_cls_name = self._plugin_name + "_cls" IDAPluginCls = compat.generate_generic_ida_plugic_cls(cls_name=plugin_cls_name) return IDAPluginCls(*args, name=self._plugin_name, interface=self, **kwargs)