Skip to content

Commit 01062f8

Browse files
committed
dev: getting click event in dev
1 parent cbd5258 commit 01062f8

7 files changed

Lines changed: 347 additions & 46 deletions

File tree

revengai/features/ai_decompiler/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def show_ai_decompiler_dialog(self, bv: BinaryView, func):
4141
self.dock_widget.raise_()
4242
log_info(f"RevEng.AI | AI Decompiler Dock already open, adding tab 0x{func:x}")
4343
if self.widget is not None:
44-
self.widget.add_tab(func)
44+
self.widget.pre_tab_setup(bv, func)
4545
return
4646

4747
self.dock_widget = QDockWidget("RevEng.AI | AI Decompiler", main_win)
Lines changed: 98 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from binaryninja import BinaryView, log_info, log_error, Symbol, SymbolType, interaction
22
from binaryninja.interaction import InteractionHandler
3-
from 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
3+
from 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
44
from concurrent.futures import ThreadPoolExecutor, as_completed
55
from typing import List, Dict, Tuple
66
import math
77
from revengai.utils.datatypes import apply_data_types as apply_data_types_util
88
import time
9-
from revengai.utils import rename_function as rename_function_util
9+
from revengai.utils import rename_function as rename_function_util, get_function_id_by_addr as get_function_id_by_addr_util
1010
from libbs.api import DecompilerInterface
1111
from libbs.decompilers.binja.interface import BinjaInterface
1212
from libbs.artifacts import _art_from_dict
@@ -18,44 +18,116 @@
1818
Struct,
1919
Typedef,
2020
)
21+
from revengai.utils.periodic_check import PeriodicChecker
22+
from PySide6.QtWidgets import QPlainTextEdit
23+
from binaryninja import BinaryView
24+
from binaryninjaui import UIContext
25+
from PySide6.QtCore import QTimer
2126

2227
class AIDecompiler:
2328
def __init__(self, config):
2429
self.config = config
30+
self._current_checker = None
31+
self._track_timer = None # Add timer instance variable
2532

33+
def stop_ai_decompiler(self):
34+
"""Stop the current AI decompiler checking"""
35+
try:
36+
if self._current_checker:
37+
self._current_checker.stop()
38+
self._current_checker = None
39+
log_info("RevEng.AI | Stopped AI decompiler")
40+
except Exception as e:
41+
log_error(f"RevEng.AI | Error stopping AI decompiler: {str(e)}")
42+
43+
def stop_tracking(self):
44+
"""Stop the active line tracking"""
45+
try:
46+
if self._track_timer:
47+
self._track_timer.stop()
48+
self._track_timer = None
49+
log_info("RevEng.AI | Stopped active line tracking")
50+
except Exception as e:
51+
log_error(f"RevEng.AI | Error stopping active line tracking: {str(e)}")
2652

27-
def get_ai_decompiler(self, bv: BinaryView, options: Dict) -> None:
53+
54+
def start_ai_decompiler(self, bv: BinaryView, options: Dict) -> None:
2855
"""Match functions from the binary against RevEng.AI database"""
2956
try:
57+
ClickMonitor()
58+
3059
log_info("RevEng.AI | Starting function searching in portal")
31-
function_addr = options.get("function", None)
60+
editor = options.get("editor")
61+
tab_name = options.get("tab_name")
62+
function = options.get("function")
63+
callback = options.get("callback")
64+
binary_id = self.config.get_binary_id(bv)
65+
function_id = get_function_id_by_addr_util(bv, function.start, binary_id)
3266

33-
functions_containing = bv.get_functions_containing(function_addr)
67+
res = RE_poll_ai_decompilation(
68+
function_id,
69+
summarise=True,
70+
).json()
3471

35-
if not functions_containing:
36-
log_error(f"RevEng.AI | Function not found at 0x{function_addr:x}")
37-
raise Exception("Function not found at address")
72+
if not res.get("status", False):
73+
callback(editor, "AI Decompilation failed.")
74+
return
3875

39-
function = functions_containing[0]
40-
log_info(f"RevEng.AI | Function: {function.name} at 0x{function.start:x} (Clicked address: 0x{function_addr:x})")
76+
poll_status = res.get("data").get("status", "uninitialised")
77+
log_info(f"RevEng.AI | Polling AI decompilation: {poll_status}")
4178

42-
binary_id = self.config.get_binary_id(bv)
43-
if not binary_id:
44-
raise Exception("Analysis not found. Please choose one using 'Choose Source' feature.")
45-
46-
analysis = RE_get_analysis_id_from_binary_id(binary_id).json()
47-
analyzed_functions = RE_get_functions_from_analysis(analysis["analysis_id"]).json()["data"]["functions"]
79+
if poll_status == "uninitialised":
80+
log_info(f"RevEng.AI | Starting AI Decompilation for function at 0x{function.start:x}")
4881

49-
analyzed_function = next((f for f in analyzed_functions if (f["function_vaddr"] + bv.image_base) == function.start), None)
50-
if not analyzed_function:
51-
log_error(f"RevEng.AI | Function {function.name} not found in analyzed functions")
52-
raise Exception("Function not found in analyzed functions")
53-
54-
url = f"https://portal.reveng.ai/function/{analyzed_function['function_id']}"
55-
InteractionHandler().open_url(url)
82+
try:
83+
res2 = RE_begin_ai_decompilation(
84+
function_id
85+
).json()
86+
except Exception as e:
87+
log_error(f"RevEng.AI | Error beginning AI decompilation: {str(e)}")
88+
callback(editor, "AI Decompilation failed.")
89+
return
90+
91+
if not res2.get("status", False):
92+
callback(editor, "AI Decompilation failed.")
93+
return
94+
95+
log_info("RevEng.AI | AI Decompilation started")
96+
97+
# Create PeriodicChecker instance (it's now a QObject)
98+
periodic_checker = PeriodicChecker()
99+
100+
# Start the AI decompiler checking with proper parameters
101+
periodic_checker.start_ai_decompiler_checking(function_id, callback, editor, tab_name)
102+
103+
# Store reference to prevent garbage collection
104+
self._current_checker = periodic_checker
56105

57-
return True, "Function found in portal"
106+
if poll_status == "success":
107+
log_info(f"RevEng.AI | AI Decompilation for function at 0x{function.start:x} is completed")
108+
callback(editor, res.get("data").get("decompilation"))
58109

59110
except Exception as e:
60-
log_error(f"RevEng.AI | Error in function matching: {str(e)}")
61-
return False, str(e)
111+
log_error(f"RevEng.AI | Error in AI decompiler: {str(e)}")
112+
return False, str(e)
113+
114+
from binaryninja import PluginCommand, BinaryView
115+
from binaryninjaui import UIContext, UIContextNotification
116+
117+
class ClickMonitor(UIContextNotification):
118+
def __init__(self):
119+
log_info("RevEng.AI | ClickMonitor initialized")
120+
super().__init__()
121+
log_info(f"RevEng.AI | class ClickMonitor")
122+
for nome in dir(self):
123+
attr = getattr(self, nome)
124+
if callable(attr) and not nome.startswith("__") and not nome.startswith("On"):
125+
log_info(f"RevEng.AI | {nome}")
126+
UIContext.registerNotification(self)
127+
128+
def OnViewChanged(self, context, view):
129+
log_info(f"RevEng.AI | View changed: {view}")
130+
131+
def OnAddressChanged(self, context, view, addr):
132+
log_info(f"RevEng.AI | User navigated to address: {hex(addr)}")
133+
Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
from binaryninja import log_error, log_info
22
from PySide6.QtWidgets import (QDockWidget, QVBoxLayout, QHBoxLayout,
3-
QPushButton, QLabel, QCheckBox, QWidget, QTabWidget)
3+
QPushButton, QLabel, QCheckBox, QWidget, QTabWidget, QPlainTextEdit)
44
from PySide6.QtCore import Qt
55
from PySide6.QtGui import QPixmap
66
from PySide6.QtWidgets import QWidget
77
from PySide6.QtCore import QCoreApplication
88
from PySide6.QtWidgets import QMessageBox
99
from PySide6.QtWidgets import QProgressBar
1010
from revengai.utils import create_progress_dialog
11+
from .c_highlighter import CHighlighter
1112
from revengai.utils.data_thread import DataThread
13+
from revengai.utils import get_function_by_addr
14+
from revengai.utils.periodic_check import PeriodicChecker
1215
import os
1316

1417
class AIDecompilerDialog(QWidget):
@@ -19,41 +22,80 @@ def __init__(self, config, ai_decompiler, bv, func):
1922
self.bv = bv
2023
self.func = func
2124
self.tabs = QTabWidget()
22-
self.init_ui(func)
25+
self.number_of_clicks = 0
26+
self.init_ui(bv, func)
2327

24-
def init_ui(self, func):
28+
def init_ui(self, bv, func):
2529
self.setWindowTitle("RevEng.AI: AI Decompiler")
2630
layout = QVBoxLayout()
2731
self.setLayout(layout)
2832
layout.addWidget(self.tabs)
29-
self.add_tab(func)
33+
self.pre_tab_setup(bv, func)
3034
self.tabs.tabCloseRequested.connect(self.close_tab)
3135

32-
def add_tab(self, tab_name):
33-
tab_name = str(f"0x{tab_name:x}")
36+
def pre_tab_setup(self, bv, func):
37+
function = get_function_by_addr(bv, func)
38+
tab_name = str(f"0x{function.start:x}")
39+
log_info(f"RevEng.AI | Given address 0x{func:x} is function: {function.name} at 0x{function.start:x}")
3440
for i in range(self.tabs.count()):
35-
log_info(f"RevEng.AI | Tab {self.tabs.tabText(i)} == {tab_name}")
3641
if self.tabs.tabText(i) == tab_name:
37-
self.tabs.setCurrentIndex(i)
42+
log_info(f"RevEng.AI | Tab {tab_name} already exists, switching to it.")
3843
return
39-
44+
4045
log_info(f"RevEng.AI | Adding tab {tab_name}")
4146
tab = QWidget()
4247
layout = QVBoxLayout()
43-
layout.addWidget(QLabel(f"{tab_name} UI goes here"))
48+
49+
editor = QPlainTextEdit()
50+
editor.setPlainText("Starting AI Decompiler...")
51+
editor.setReadOnly(True)
52+
53+
#CHighlighter(editor.document())
54+
editor.show()
55+
layout.addWidget(editor)
4456
tab.setLayout(layout)
57+
4558
index = self.tabs.addTab(tab, tab_name)
4659
self.tabs.setCurrentIndex(index)
4760

4861
if self.tabs.count() > 1:
4962
self.tabs.setTabsClosable(True)
63+
64+
options = {
65+
"editor": editor,
66+
"tab_name": tab_name,
67+
"function": function,
68+
"callback": self.edit_editor
69+
}
70+
self.ai_decompiler.start_ai_decompiler(self.bv, options)
5071

5172
def close_tab(self, index):
5273
log_info(f"RevEng.AI | Closing tab {index} of {self.tabs.count()} tabs")
74+
75+
# Stop AI decompiler for this tab if it's running
76+
try:
77+
self.ai_decompiler.stop_ai_decompiler()
78+
except Exception as e:
79+
log_error(f"RevEng.AI | Error stopping AI decompiler on tab close: {str(e)}")
80+
5381
self.tabs.removeTab(index)
5482
if self.tabs.count() == 1:
5583
self.tabs.setTabsClosable(False)
5684

85+
def closeEvent(self, event):
86+
"""Handle dialog close event"""
87+
try:
88+
# Stop any running AI decompiler
89+
if hasattr(self, 'ai_decompiler'):
90+
self.ai_decompiler.stop_ai_decompiler()
91+
except Exception as e:
92+
log_error(f"RevEng.AI | Error during cleanup: {str(e)}")
5793

58-
94+
super().closeEvent(event)
95+
96+
def edit_editor(self, editor, text):
97+
editor.setPlainText(text)
98+
CHighlighter(editor.document())
99+
100+
59101

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from PySide6.QtGui import QSyntaxHighlighter, QTextCharFormat, QColor, QFont
2+
from PySide6.QtCore import QRegularExpression
3+
4+
class CHighlighter(QSyntaxHighlighter):
5+
def __init__(self, document = None):
6+
super().__init__(document)
7+
8+
def fmt(color, bold=False):
9+
f = QTextCharFormat()
10+
f.setForeground(QColor(color))
11+
if bold:
12+
f.setFontWeight(QFont.Bold)
13+
return f
14+
15+
self.highlighting_rules = []
16+
17+
# Keywords
18+
keywords = [
19+
"int", "char", "float", "double", "void", "if", "else", "while", "for", "return",
20+
"switch", "case", "break", "continue", "struct", "typedef", "const", "unsigned",
21+
"signed", "long", "short", "static", "volatile", "enum", "union", "do", "goto", "sizeof"
22+
]
23+
keyword_format = fmt("#569CD6", True)
24+
for word in keywords:
25+
pattern = QRegularExpression(rf"\b{word}\b")
26+
self.highlighting_rules.append((pattern, keyword_format))
27+
28+
# Types (optional C standard types)
29+
type_format = fmt("#4EC9B0")
30+
types = ["uint32_t", "int64_t", "uint8_t", "size_t"]
31+
for t in types:
32+
pattern = QRegularExpression(rf"\b{t}\b")
33+
self.highlighting_rules.append((pattern, type_format))
34+
35+
# Strings
36+
string_format = fmt("#CE9178")
37+
self.highlighting_rules.append((QRegularExpression(r'"[^"\\]*(\\.[^"\\]*)*"'), string_format))
38+
39+
# Char literals
40+
char_format = fmt("#CE9178")
41+
self.highlighting_rules.append((QRegularExpression(r"'.'"), char_format))
42+
43+
# Comments
44+
comment_format = fmt("#6A9955")
45+
self.highlighting_rules.append((QRegularExpression(r"//.*"), comment_format))
46+
self.comment_start = QRegularExpression(r"/\*")
47+
self.comment_end = QRegularExpression(r"\*/")
48+
self.comment_format = comment_format
49+
50+
# Numbers
51+
number_format = fmt("#B5CEA8")
52+
self.highlighting_rules.append((QRegularExpression(r"\b[0-9]+[uUlL]*\b"), number_format))
53+
54+
def highlightBlock(self, text):
55+
for pattern, fmt in self.highlighting_rules:
56+
iterator = pattern.globalMatch(text)
57+
while iterator.hasNext():
58+
match = iterator.next()
59+
start, length = match.capturedStart(), match.capturedLength()
60+
self.setFormat(start, length, fmt)
61+
62+
# Multiline comment highlighting
63+
self.setCurrentBlockState(0)
64+
start_index = 0
65+
if self.previousBlockState() != 1:
66+
match = self.comment_start.match(text)
67+
start_index = match.capturedStart() if match.hasMatch() else -1
68+
69+
while start_index >= 0:
70+
match_end = self.comment_end.match(text, start_index)
71+
end_index = match_end.capturedEnd() if match_end.hasMatch() else -1
72+
73+
if end_index == -1:
74+
self.setCurrentBlockState(1)
75+
comment_length = len(text) - start_index
76+
else:
77+
comment_length = end_index - start_index
78+
79+
self.setFormat(start_index, comment_length, self.comment_format)
80+
start_index = self.comment_start.match(text, start_index + comment_length).capturedStart()

revengai/utils/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from .periodic_check import PeriodicChecker
22
from .base_auth_feature import BaseAuthFeature
33
from .progress_dialog import create_progress_dialog, create_cancellable_progress_dialog
4-
from .utils import rename_function, parse_date
4+
from .utils import rename_function, parse_date, get_function_by_addr, get_function_id_by_addr
55
from .datatypes import apply_data_types
66

7-
__all__ = ['PeriodicChecker', 'BaseAuthFeature', 'create_progress_dialog', 'create_cancellable_progress_dialog', 'rename_function', 'parse_date', 'apply_data_types']
7+
8+
__all__ = ['PeriodicChecker', 'BaseAuthFeature', 'create_progress_dialog', 'create_cancellable_progress_dialog', 'rename_function', 'parse_date', 'apply_data_types', 'get_function_by_addr', 'get_function_id_by_addr']

0 commit comments

Comments
 (0)