55from PySide6 .QtWidgets import QPlainTextEdit
66from PySide6 .QtCore import QTimer , QObject , Signal
77
8+ _STATUS_UNINITIALISED = "UNINITIALISED"
9+ _STATUS_COMPLETED = "COMPLETED"
10+ _STATUS_FAILED = "FAILED"
11+
12+ _PHASE_DECOMP = "decomp"
13+ _PHASE_SUMMARY = "summary"
14+ _PHASE_COMMENTS = "comments"
15+ _PHASE_DONE = "done"
16+
17+
18+ def _normalise_status (status ) -> str :
19+ return str (status ).strip ().upper ()
20+
21+
22+ def _format_summary_as_comment (summary : str ) -> str :
23+ body = "\n " .join (f" * { line } " for line in summary .strip ().splitlines ())
24+ return f"/*\n { body } \n */"
25+
26+
27+ def _inject_inline_comments (code : str , comments ) -> str :
28+ lines = code .split ("\n " )
29+ for c in sorted (comments , key = lambda x : x .line , reverse = True ):
30+ idx = c .line - 1
31+ if idx < 0 or idx >= len (lines ):
32+ continue
33+ target = lines [idx ]
34+ indent = target [: len (target ) - len (target .lstrip ())]
35+ lines .insert (idx , f"{ indent } // { c .comment } " )
36+ return "\n " .join (lines )
37+
38+
839class AIDecompilerChecker (QObject ):
9- # Signal for thread-safe UI updates
1040 update_text_signal = Signal (object , str )
11-
41+
1242 def __init__ (self ):
1343 super ().__init__ ()
1444 self ._current_timer : Optional [Timer ] = None
1545 self ._ai_decompiler_timer : Optional [QTimer ] = None
1646 self .number_of_clicks = 0
1747 self .flag = False
18- # Connect signal to slot for safe UI updates
48+ self ._function_id : Optional [int ] = None
49+ self ._phase = _PHASE_DECOMP
50+ self ._decompilation : Optional [str ] = None
51+ self ._summary_text : Optional [str ] = None
52+ self ._inline_comments = None
53+ self ._summary_requeued = False
54+ self ._comments_requeued = False
1955 self .update_text_signal .connect (self ._update_text_slot )
2056
2157 def _update_text_slot (self , callback , text ):
22- """Slot that runs in the main thread to safely update UI"""
2358 try :
2459 if hasattr (callback , '__call__' ):
25- # If callback is a function, call it with editor and text
26- # We need to get the editor from somewhere - let's store it
2760 if hasattr (self , '_current_editor' ):
2861 callback (self ._current_editor , text )
2962 except Exception as ex :
@@ -34,93 +67,167 @@ def stop(self):
3467 self ._current_timer .cancel ()
3568 self ._current_timer = None
3669 log_info ("RevEng.AI | Stopped periodic status check" )
37-
70+
3871 if self ._ai_decompiler_timer :
3972 self ._ai_decompiler_timer .stop ()
4073 self ._ai_decompiler_timer = None
4174 log_info ("RevEng.AI | Stopped AI decompiler periodic check" )
4275
4376 def start_ai_decompiler_checking (self , function_id : int , callback , editor : QPlainTextEdit , name : str , config ) -> None :
44- """Start periodic checking for AI decompiler with thread-safe UI updates"""
4577 try :
46-
47- # Store editor reference for the signal slot
4878 log_info (f"RevEng.AI | Starting AI decompiler periodic check for: { name } " )
4979 self ._current_editor = editor
5080 self ._current_callback = callback
5181 self ._current_config = config
52- # Stop any existing timer
82+
83+ self ._function_id = function_id
84+ self ._phase = _PHASE_DECOMP
85+ self ._decompilation = None
86+ self ._summary_text = None
87+ self ._inline_comments = None
88+ self ._summary_requeued = False
89+ self ._comments_requeued = False
90+
5391 if self ._ai_decompiler_timer :
5492 self ._ai_decompiler_timer .stop ()
55-
56- # Create QTimer for thread-safe execution
93+
5794 self ._ai_decompiler_timer = QTimer ()
58- self ._ai_decompiler_timer .timeout .connect (lambda : self ._ai_decompiler_worker (function_id , name , callback , editor ))
59-
60- # Start the timer with 5 second intervals
61- self ._ai_decompiler_timer .start (1000 ) # 1000 ms = 1 second
62-
95+ self ._ai_decompiler_timer .timeout .connect (self ._ai_decompiler_worker )
96+ self ._ai_decompiler_timer .start (1000 )
97+
6398 log_info (f"RevEng.AI | Started AI decompiler periodic check for: { name } " )
64-
99+
100+ self ._ai_decompiler_worker ()
101+
65102 except Exception as ex :
66103 log_error (f"RevEng.AI | Error starting AI decompiler check: { str (ex )} " )
67-
68- def _ai_decompiler_worker (self , function_id : int , name : str , callback , editor : QPlainTextEdit ):
69- """Worker method that runs in a separate thread via QTimer"""
104+
105+ def _api (self , api_client ):
106+ return revengai .FunctionsAIDecompilationApi (api_client )
107+
108+ def _render (self ):
109+ parts = []
110+ if self ._summary_text :
111+ parts .append (_format_summary_as_comment (self ._summary_text ))
112+
113+ body = self ._decompilation or ""
114+ if self ._inline_comments :
115+ body = _inject_inline_comments (body , self ._inline_comments )
116+
117+ text = "\n " .join (parts + [body ]) if parts else body
118+ self .update_text_signal .emit (self ._current_callback , text )
119+
120+ def _run_decomp_phase (self ):
121+ fid = self ._function_id
122+ with self ._current_config .create_api_client () as api_client :
123+ status = _normalise_status (self ._api (api_client ).get_ai_decompilation_status (fid ).status )
124+ log_info (f"RevEng.AI | AI Decompilation status for Function ID { fid } : { status } " )
125+
126+ if status == _STATUS_UNINITIALISED :
127+ with self ._current_config .create_api_client () as api_client :
128+ response = self ._api (api_client ).create_ai_decompilation (fid )
129+ if not getattr (response , "status" , False ):
130+ raise Exception (f"AI Decompilation for Function ID { fid } failed to start" )
131+ log_info (f"RevEng.AI | AI Decompilation task created for Function ID { fid } " )
132+ return
133+
134+ if status == _STATUS_FAILED :
135+ raise Exception (f"AI Decompilation for Function ID { fid } failed" )
136+
137+ if status != _STATUS_COMPLETED :
138+ return
139+
140+ with self ._current_config .create_api_client () as api_client :
141+ self ._decompilation = self ._api (api_client ).get_ai_decompilation (fid ).decompilation
142+ log_info (f"RevEng.AI | AI Decompilation for Function ID { fid } completed" )
143+ self ._render ()
144+ self ._phase = _PHASE_SUMMARY
145+
146+ def _run_summary_phase (self ):
147+ fid = self ._function_id
70148 try :
71- if self .flag :
72- log_info (f"RevEng.AI | AI Decompilation is already in progress" )
149+ with self ._current_config .create_api_client () as api_client :
150+ summary = self ._api (api_client ).get_ai_decompilation_summary (fid )
151+ status = _normalise_status (summary .task_status )
152+
153+ if status == _STATUS_COMPLETED :
154+ self ._summary_text = summary .ai_summary
155+ self ._render ()
156+ self ._phase = _PHASE_COMMENTS
157+ return
158+
159+ if status == _STATUS_FAILED :
160+ log_error (f"RevEng.AI | AI summary generation failed for Function ID { fid } " )
161+ self ._phase = _PHASE_COMMENTS
73162 return
74- self .flag = True
75163
164+ if status == _STATUS_UNINITIALISED and not self ._summary_requeued :
165+ self ._requeue (self ._api_regenerate_summary , fid , "summary" )
166+ self ._summary_requeued = True
167+
168+ except Exception as ex :
169+ log_error (f"RevEng.AI | Error fetching AI summary for Function ID { fid } : { str (ex )} " )
170+ self ._phase = _PHASE_COMMENTS
171+
172+ def _run_comments_phase (self ):
173+ fid = self ._function_id
174+ try :
76175 with self ._current_config .create_api_client () as api_client :
77- api_instance = revengai .FunctionsAIDecompilationApi (api_client )
78- api_response_status = api_instance .get_ai_decompilation_task_status (function_id )
79- log_info (f"RevEng.AI | AI Decompilation task created for Function ID: { function_id } " )
80-
81- poll_status = api_response_status .data .status
82- if not poll_status :
83- raise Exception ("AI Decompilation task not found." )
84-
85- log_info (f"RevEng.AI | AI Decompilation for Function ID: { function_id } is { poll_status } " )
86-
87- if poll_status .lower () == "uninitialised" :
88- log_info (f"RevEng.AI | Starting AI Decompilation for Function ID: { function_id } " )
89- try :
90- with self ._current_config .create_api_client () as api_client :
91- api_instance = revengai .FunctionsAIDecompilationApi (api_client )
92- api_response = api_instance .create_ai_decompilation_task (function_id )
93- if not api_response .status :
94- raise Exception (f"AI Decompilation for Function ID: { function_id } failed" )
95- log_info (f"RevEng.AI | AI Decompilation task created for Function ID: { function_id } " )
96-
97- except Exception as e :
98- log_error (f"RevEng.AI | Error beginning AI decompilation: { str (e )} " )
99- raise Exception (f"AI Decompilation for Function ID: { function_id } failed" )
100-
101- elif poll_status .lower () == "failed" :
102- raise Exception (f"AI Decompilation for Function ID: { function_id } failed" )
103-
104- elif poll_status .lower () != "completed" :
105- log_info (f"RevEng.AI | AI Decompilation for Function ID: { function_id } is not completed" )
106- self .flag = False
176+ comments = self ._api (api_client ).get_ai_decompilation_inline_comments (fid )
177+ status = _normalise_status (comments .task_status )
178+
179+ if status == _STATUS_COMPLETED :
180+ self ._inline_comments = comments .inline_comments
181+ self ._render ()
182+ self ._finish ()
107183 return
108-
109- self .flag = False
110- log_info (f"RevEng.AI | AI Decompilation for Function ID: { function_id } is completed" )
184+
185+ if status == _STATUS_FAILED :
186+ log_error (f"RevEng.AI | Inline comment generation failed for Function ID { fid } " )
187+ self ._finish ()
188+ return
189+
190+ if status == _STATUS_UNINITIALISED and not self ._comments_requeued :
191+ self ._requeue (self ._api_regenerate_comments , fid , "inline comments" )
192+ self ._comments_requeued = True
193+
194+ except Exception as ex :
195+ log_error (f"RevEng.AI | Error fetching inline comments for Function ID { fid } : { str (ex )} " )
196+ self ._finish ()
197+
198+ def _api_regenerate_summary (self , api_client , fid ):
199+ self ._api (api_client ).regenerate_ai_decompilation_summary (fid )
200+
201+ def _api_regenerate_comments (self , api_client , fid ):
202+ self ._api (api_client ).regenerate_ai_decompilation_inline_comments (fid )
203+
204+ def _requeue (self , regenerate_fn , fid , label ):
205+ try :
111206 with self ._current_config .create_api_client () as api_client :
112- api_instance = revengai .FunctionsAIDecompilationApi (api_client )
113- api_response = api_instance .get_ai_decompilation_task_result (function_id , summarise = True , generate_inline_comments = True )
114-
115- # Safely update UI through signal/slot mechanism
116- self .update_text_signal .emit (self ._current_callback , api_response .data .decompilation )
207+ regenerate_fn (api_client , fid )
208+ log_info (f"RevEng.AI | Requested { label } generation for Function ID { fid } " )
209+ except Exception as ex :
210+ log_info (f"RevEng.AI | Could not (re)queue { label } for Function ID { fid } : { str (ex )} " )
117211
118- # Stop timer from main thread
119- self .update_text_signal .emit (lambda x ,y : self .stop (), "" )
212+ def _finish (self ):
213+ self ._phase = _PHASE_DONE
214+ self .update_text_signal .emit (lambda x , y : self .stop (), "" )
120215
216+ def _ai_decompiler_worker (self ):
217+ if self .flag :
218+ log_info ("RevEng.AI | AI Decompilation tick already in progress" )
219+ return
220+ self .flag = True
221+ try :
222+ if self ._phase == _PHASE_DECOMP :
223+ self ._run_decomp_phase ()
224+ elif self ._phase == _PHASE_SUMMARY :
225+ self ._run_summary_phase ()
226+ elif self ._phase == _PHASE_COMMENTS :
227+ self ._run_comments_phase ()
121228 except Exception as ex :
122- self .flag = False
123229 log_error (f"RevEng.AI | Error in AI decompiler worker: { str (ex )} " )
124230 self .update_text_signal .emit (self ._current_callback , f"AI Decompilation failed: { ex } " )
125- # Stop timer from main thread
126- self .update_text_signal .emit (lambda x ,y : self .stop (), "" )
231+ self ._finish ()
232+ finally :
233+ self .flag = False
0 commit comments