22from binaryninja .interaction import InteractionHandler
33from reait .api import RE_authentication , RE_search , RE_nearest_symbols_batch , RE_analyze_functions , RE_name_score , RE_functions_data_types , RE_functions_data_types_poll , RE_get_analysis_id_from_binary_id , RE_get_functions_from_analysis , RE_poll_ai_decompilation , RE_begin_ai_decompilation
44from concurrent .futures import ThreadPoolExecutor , as_completed
5- from typing import List , Dict , Tuple
5+ from typing import List , Dict , Tuple , Optional , Callable
66import math
77from revengai .utils .datatypes import apply_data_types as apply_data_types_util
88import time
2121from revengai .utils .periodic_check import PeriodicChecker
2222from PySide6 .QtWidgets import QPlainTextEdit
2323from binaryninja import BinaryView
24- from binaryninjaui import UIContext
24+ from binaryninjaui import UIContext , UIContextNotification
2525from PySide6 .QtCore import QTimer
2626
27+ class AddressChangeMonitor (UIContextNotification ):
28+ """
29+ Monitors address changes in Binary Ninja's UI and calls a callback function
30+ when the user navigates to a new address in the decompiler view.
31+ """
32+
33+ def __init__ (self , callback : Optional [Callable ] = None ):
34+ """
35+ Initialize the address change monitor.
36+
37+ Args:
38+ callback: Optional callback function to call when address changes.
39+ Should accept parameters: (context, view, address)
40+ """
41+ super ().__init__ ()
42+ self .callback = callback
43+ self ._registered = False
44+ self ._last_address = None
45+
46+ # Register for notifications
47+ self .register ()
48+
49+ def register (self ):
50+ """Register this notification with the UI context"""
51+ if not self ._registered :
52+ UIContext .registerNotification (self )
53+ self ._registered = True
54+ log_info ("RevEng.AI | AddressChangeMonitor registered for notifications" )
55+
56+ def unregister (self ):
57+ """Unregister this notification from the UI context"""
58+ if self ._registered :
59+ UIContext .unregisterNotification (self )
60+ self ._registered = False
61+ log_info ("RevEng.AI | AddressChangeMonitor unregistered from notifications" )
62+
63+ def set_callback (self , callback : Callable ):
64+ """Set or update the callback function"""
65+ self .callback = callback
66+ log_info ("RevEng.AI | AddressChangeMonitor callback updated" )
67+
68+ def OnViewChange (self , context , view , frame ):
69+ """Called when the view changes"""
70+ log_info (f"RevEng.AI | OnViewChange called: { view } " )
71+ if self .callback :
72+ try :
73+ self .callback (context , view , None , "view_changed" )
74+ except Exception as e :
75+ log_error (f"RevEng.AI | Error in view change callback: { str (e )} " )
76+
77+ def OnAddressChange (self , context , view , frame , addr ):
78+ """Called when the user navigates to a new address"""
79+ # addr is a ViewLocation object, not a simple integer
80+ try :
81+ # Extract the address from the ViewLocation object
82+ if hasattr (addr , 'addr' ):
83+ current_addr = addr .addr
84+ elif hasattr (addr , 'address' ):
85+ current_addr = addr .address
86+ else :
87+ current_addr = addr
88+
89+ log_info (f"RevEng.AI | OnAddressChange called: { addr } (address: 0x{ current_addr :x} )" )
90+
91+ # Avoid duplicate notifications for the same address
92+ if current_addr == self ._last_address :
93+ return
94+
95+ self ._last_address = current_addr
96+
97+ if self .callback :
98+ try :
99+ self .callback (context , view , current_addr , "address_changed" )
100+ except Exception as e :
101+ log_error (f"RevEng.AI | Error in address change callback: { str (e )} " )
102+ except Exception as e :
103+ log_error (f"RevEng.AI | Error processing address change: { str (e )} " )
104+ log_info (f"RevEng.AI | OnAddressChange called with addr type: { type (addr )} , value: { addr } " )
105+
106+ def OnFunctionChange (self , context , view , frame , func ):
107+ """Called when the current function changes"""
108+ if func :
109+ log_info (f"RevEng.AI | OnFunctionChange called: { func .name } at 0x{ func .start :x} " )
110+ if self .callback :
111+ try :
112+ self .callback (context , view , func .start , "function_changed" )
113+ except Exception as e :
114+ log_error (f"RevEng.AI | Error in function change callback: { str (e )} " )
115+
116+ class TimerBasedAddressMonitor :
117+ """
118+ Alternative address monitor that uses a timer to periodically check
119+ the current address in the active context.
120+ """
121+
122+ def __init__ (self , callback : Optional [Callable ] = None , interval : int = 100 ):
123+ """
124+ Initialize the timer-based address monitor.
125+
126+ Args:
127+ callback: Optional callback function to call when address changes.
128+ interval: Polling interval in milliseconds (default: 100ms)
129+ """
130+ self .callback = callback
131+ self ._last_address = None
132+ self ._timer = QTimer ()
133+ self ._timer .timeout .connect (self ._check_address )
134+ self ._interval = interval
135+ self ._active = False
136+
137+ def start (self ):
138+ """Start monitoring address changes"""
139+ if not self ._active :
140+ self ._timer .start (self ._interval )
141+ self ._active = True
142+ log_info (f"RevEng.AI | TimerBasedAddressMonitor started (interval: { self ._interval } ms)" )
143+
144+ def stop (self ):
145+ """Stop monitoring address changes"""
146+ if self ._active :
147+ self ._timer .stop ()
148+ self ._active = False
149+ self ._last_address = None
150+ log_info ("RevEng.AI | TimerBasedAddressMonitor stopped" )
151+
152+ def set_callback (self , callback : Callable ):
153+ """Set or update the callback function"""
154+ self .callback = callback
155+ log_info ("RevEng.AI | TimerBasedAddressMonitor callback updated" )
156+
157+ def _check_address (self ):
158+ """Check the current address and call callback if it changed"""
159+ try :
160+ ctx = UIContext .activeContext ()
161+ if not ctx :
162+ return
163+
164+ # Try to get the current address from the context
165+ current_addr = None
166+
167+ # Method 1: Try to get from the current view
168+ try :
169+ view = ctx .getCurrentView ()
170+ if view :
171+ current_addr = view .getCurrentAddress ()
172+ except :
173+ pass
174+
175+ # Method 2: Try to get from the current function
176+ if current_addr is None :
177+ try :
178+ func = ctx .getCurrentFunction ()
179+ if func :
180+ current_addr = func .start
181+ except :
182+ pass
183+
184+ # Method 3: Try to get from the current binary view
185+ if current_addr is None :
186+ try :
187+ bv = ctx .getCurrentBinaryView ()
188+ if bv :
189+ # Get the current function's address
190+ funcs = list (bv .functions )
191+ if funcs :
192+ current_addr = funcs [0 ].start
193+ except :
194+ pass
195+
196+ if current_addr is not None and current_addr != self ._last_address :
197+ self ._last_address = current_addr
198+ log_info (f"RevEng.AI | Timer detected address change to: 0x{ current_addr :x} " )
199+
200+ if self .callback :
201+ try :
202+ self .callback (ctx , None , current_addr , "address_changed" )
203+ except Exception as e :
204+ log_error (f"RevEng.AI | Error in timer-based address change callback: { str (e )} " )
205+
206+ except Exception as e :
207+ log_error (f"RevEng.AI | Error in timer-based address check: { str (e )} " )
208+
27209class AIDecompiler :
28210 def __init__ (self , config ):
29211 self .config = config
30212 self ._current_checker = None
31- self ._track_timer = None # Add timer instance variable
213+ self ._track_timer = None
214+ self ._address_monitor = None # Store the address monitor instance
215+ self ._timer_monitor = None # Store the timer-based monitor instance
32216
33217 def stop_ai_decompiler (self ):
34218 """Stop the current AI decompiler checking"""
@@ -50,11 +234,90 @@ def stop_tracking(self):
50234 except Exception as e :
51235 log_error (f"RevEng.AI | Error stopping active line tracking: { str (e )} " )
52236
237+ def start_address_tracking (self , callback : Optional [Callable ] = None , use_timer : bool = True ):
238+ """
239+ Start tracking address changes in the Binary Ninja UI.
240+
241+ Args:
242+ callback: Optional callback function to call when address changes.
243+ Should accept parameters: (context, view, address, change_type)
244+ where change_type can be "address_changed", "function_changed", or "view_changed"
245+ use_timer: Whether to also use timer-based monitoring as a fallback
246+ """
247+ try :
248+ # Stop any existing monitors
249+ self .stop_address_tracking ()
250+
251+ # Create notification-based monitor
252+ self ._address_monitor = AddressChangeMonitor (callback )
253+ log_info ("RevEng.AI | Started notification-based address tracking" )
254+
255+ # Create timer-based monitor as fallback if requested
256+ if use_timer :
257+ self ._timer_monitor = TimerBasedAddressMonitor (callback )
258+ self ._timer_monitor .start ()
259+ log_info ("RevEng.AI | Started timer-based address tracking" )
260+
261+ except Exception as e :
262+ log_error (f"RevEng.AI | Error starting address tracking: { str (e )} " )
263+
264+ def stop_address_tracking (self ):
265+ """Stop tracking address changes"""
266+ try :
267+ if self ._address_monitor :
268+ self ._address_monitor .unregister ()
269+ self ._address_monitor = None
270+ log_info ("RevEng.AI | Stopped notification-based address tracking" )
271+
272+ if self ._timer_monitor :
273+ self ._timer_monitor .stop ()
274+ self ._timer_monitor = None
275+ log_info ("RevEng.AI | Stopped timer-based address tracking" )
276+
277+ except Exception as e :
278+ log_error (f"RevEng.AI | Error stopping address tracking: { str (e )} " )
279+
280+ def set_address_tracking_callback (self , callback : Callable ):
281+ """
282+ Set or update the callback for address tracking.
283+
284+ Args:
285+ callback: Function to call when address changes.
286+ Should accept parameters: (context, view, address, change_type)
287+ """
288+ try :
289+ if self ._address_monitor :
290+ self ._address_monitor .set_callback (callback )
291+ if self ._timer_monitor :
292+ self ._timer_monitor .set_callback (callback )
293+ else :
294+ # If no monitor exists, create one
295+ self .start_address_tracking (callback )
296+ except Exception as e :
297+ log_error (f"RevEng.AI | Error setting address tracking callback: { str (e )} " )
53298
54299 def start_ai_decompiler (self , bv : BinaryView , options : Dict ) -> None :
55300 """Match functions from the binary against RevEng.AI database"""
56301 try :
57- ClickMonitor ()
302+ # Example of how to use address tracking with AI decompiler
303+ def address_change_callback (context , view , addr , change_type ):
304+ """Example callback for address changes"""
305+ if change_type == "address_changed" and addr is not None :
306+ log_info (f"RevEng.AI | Address changed to 0x{ addr :x} - could trigger AI decompilation here" )
307+ # You can add your custom logic here, such as:
308+ # - Automatically starting AI decompilation for the new function
309+ # - Updating UI elements
310+ # - Triggering other analysis
311+
312+ # Example: Get function at the new address
313+ if bv :
314+ functions = bv .get_functions_containing (addr )
315+ if functions :
316+ function = functions [0 ]
317+ log_info (f"RevEng.AI | Function at new address: { function .name } at 0x{ function .start :x} " )
318+
319+ # Start address tracking with both notification and timer-based monitoring
320+ self .start_address_tracking (address_change_callback , use_timer = True )
58321
59322 log_info ("RevEng.AI | Starting function searching in portal" )
60323 editor = options .get ("editor" )
@@ -107,13 +370,15 @@ def start_ai_decompiler(self, bv: BinaryView, options: Dict) -> None:
107370 log_info (f"RevEng.AI | AI Decompilation for function at 0x{ function .start :x} is completed" )
108371 callback (editor , res .get ("data" ).get ("decompilation" ))
109372
373+ if poll_status == "error" :
374+ log_info (f"RevEng.AI | AI Decompilation for function at 0x{ function .start :x} failed" )
375+ callback (editor , "AI Decompilation failed." )
376+
110377 except Exception as e :
111378 log_error (f"RevEng.AI | Error in AI decompiler: { str (e )} " )
112379 return False , str (e )
113-
114- from binaryninja import PluginCommand , BinaryView
115- from binaryninjaui import UIContext , UIContextNotification
116380
381+ # Legacy ClickMonitor class for backward compatibility
117382class ClickMonitor (UIContextNotification ):
118383 def __init__ (self ):
119384 log_info ("RevEng.AI | ClickMonitor initialized" )
0 commit comments