44from PyQt6 .QtCore import Qt , pyqtSlot , QThread
55from PyQt6 .QtGui import QPalette
66from PyQt6 .QtWidgets import QFileDialog , QApplication
7+ import r2pipe
78
89import diff_ui
910
@@ -25,9 +26,6 @@ def run(self) -> None:
2526 line = ''
2627 line_count = 0
2728
28- dark_mode = self .is_dark_mode ()
29- default_color = "white" if dark_mode else "black"
30-
3129 for block_start , (block1 , block2 ) in self .formatted_diffs .items ():
3230 line_count += 1
3331 if line_count == 1 :
@@ -68,7 +66,7 @@ class BinaryCompareWorker(QThread):
6866 binary_compare_finished_sig = QtCore .pyqtSignal (str )
6967
7068 """Comparing two binary files"""
71- def __init__ (self , file1 , file2 ):
69+ def __init__ (self , file1 , file2 , sections ):
7270 """Get the files to compare and initialise message, offset and diff list.
7371 :param file1: a file
7472 :type file1: string
@@ -89,64 +87,80 @@ def __init__(self, file1, file2):
8987 self .file1_contents = None
9088 self .file2_contents = None
9189
90+ self .sections = sections
91+
9292 self .offset_differs = None
9393
94- def read_file_contents (self , filename ):
94+ def read_file_contents (self , filename , start , size ):
9595 '''Read file contents into a list of bytes.'''
9696 with open (filename , 'rb' ) as file :
97- return list (file .read ())
97+ if start is not None :
98+ file .seek (start )
99+ try :
100+ return list (file .read (size ))
101+ except Exception as e :
102+ return list ()
103+ else :
104+ try :
105+ return list (file .read ())
106+ except Exception as e :
107+ return list ()
98108
99109 def run (self ) -> None :
100110 """Compare the two files
101- :returns: Comparison result: True if similar, False if different.
102- Set vars offset and message if there's a difference.
103- """
111+ :returns: Comparison result: True if similar, False if different.
112+ Set vars offset and message if there's a difference.
113+ """
104114 self .message = None
105115 self .offset_differs = None
106116 offset = 0
107- offset_diff = 0
108- first = False
109117 if not os .path .isfile (self .file1 ) or not os .path .isfile (self .file2 ):
110118 self .message = "not found"
111119 return
112120 if os .path .getsize (self .file1 ) != os .path .getsize (self .file2 ):
113121 self .message = "size"
114122 self .binary_compare_finished_sig .emit ("size" )
115123 return
124+ self .binary_compare_finished_sig .emit ("start" )
116125 result = True
117- f1 = open (self .file1 , 'rb' )
118- f2 = open (self .file2 , 'rb' )
119- loop = True
120- while loop :
121- buffer1 = f1 .read (self ._buffer_size )
122- buffer2 = f2 .read (self ._buffer_size )
123- if len (buffer1 ) == 0 or len (buffer2 ) == 0 :
124- loop = False
125- for byte1 , byte2 in zip (buffer1 , buffer2 ):
126+ self .diff_list .clear ()
127+
128+ # Compare each section
129+ for section in self .sections :
130+ start = section ['start' ]
131+ size = section ['size' ]
132+
133+ # Read section contents
134+ data1 = self .read_file_contents (self .file1 , start , size )
135+ data2 = self .read_file_contents (self .file2 , start , size )
136+
137+ if not data1 or not data2 :
138+ self .message = "cannot read"
139+ self .binary_compare_finished_sig .emit (self .message )
140+ return
141+
142+ # Compare the contents byte by byte
143+ for offset , (byte1 , byte2 ) in enumerate (zip (data1 , data2 ), start = start if start is not None else 0 ):
126144 if byte1 != byte2 :
127- if first == False :
128- first = True
129145 result = False
130146 self .diff_list .append ((offset , byte1 , byte2 ))
131- offset += 1
132- if not first :
133- offset_diff += 1
134- f1 .close ()
135- f2 .close ()
136- if not result :
137- self .message = 'content'
138- self .offset = hex (offset_diff )
139- else :
140- self .message = 'identical'
147+
148+ if not result :
149+ self .message = "content"
150+ self .offset = hex (offset )
151+
152+ # Sort the diff_list by offset in ascending order
153+ self .diff_list .sort (key = lambda x : x [0 ])
141154
142155 # Read file contents into lists after comparison
143- self .file1_contents = self .read_file_contents (self .file1 )
144- self .file2_contents = self .read_file_contents (self .file2 )
156+ self .file1_contents = self .read_file_contents (self .file1 , None , None )
157+ self .file2_contents = self .read_file_contents (self .file2 , None , None )
145158
146159 if result :
147- self .binary_compare_finished_sig . emit ( "identical" )
160+ self .message = "identical"
148161 else :
149- self .binary_compare_finished_sig .emit ("finished" )
162+ self .message = "finished"
163+ self .binary_compare_finished_sig .emit (self .message )
150164
151165 def format_diff (self , block_size = 16 ):
152166 """Format the differences in blocks of specified size."""
@@ -172,14 +186,15 @@ def __init__(self, statusBar):
172186 super (DiffDialogClass , self ).__init__ (statusBar )
173187 self .diff_dialog = QtWidgets .QDialog ()
174188 self .diff_dialog .setWindowFlags (Qt .WindowType .WindowStaysOnTopHint )
175- # self.diff_dialog.setWindowModality(Qt.WindowModality.ApplicationModal)
176189 self .diff_dialog_ui = diff_ui .Ui_DiffDialog ()
177190 self .diff_dialog_ui .setupUi (self .diff_dialog )
178191
179192 self .statusBar = statusBar
180193
181194 self .file1 = None
182195 self .file2 = None
196+ self .sections = None
197+ self .checked_sections = []
183198
184199 self .diff_dialog_ui .textEdit .file_dropped_sig .connect (self .file_dropped_sig_func )
185200 self .diff_dialog_ui .textEdit_2 .file_dropped_sig .connect (self .file_dropped_sig_func )
@@ -190,46 +205,46 @@ def __init__(self, statusBar):
190205 self .diff_dialog_ui .doDiffBtn .setDisabled (True )
191206 self .diff_dialog_ui .doDiffBtn .clicked .connect (self .do_diff )
192207
193- self .binary_diff_result_window = None
194- self .binary_diff_result_ui = None
195208 self .binary_compare_worker = None
196209
210+ self .binary_diff_result_window = QtWidgets .QWidget ()
211+ self .binary_diff_result_ui = diff_ui .CompareFilesWindow ()
212+ self .binary_diff_result_ui .setupUi (self .binary_diff_result_window )
213+ self .binary_diff_result_ui .stopDiffBtn .clicked .connect (self .stop_diff )
214+
197215 self .process_diff_result_worker = None
198216 self .process_diff_result_sig_count = 0
199217
200218 self .diff_result = None
201219
202220 @pyqtSlot (list )
203- def file_dropped_sig_func (self , dropped_file : list ):
204- if dropped_file [0 ] == "file1" :
205- self .file1 = dropped_file [1 ]
206- elif dropped_file [0 ] == "file2" :
207- self .file2 = dropped_file [1 ]
208-
209- if self .file1 and self .file2 :
210- self .diff_dialog_ui .doDiffBtn .setEnabled (True )
211-
212- def select_file (self , file1or2 ):
213- file , _ = QFileDialog .getOpenFileNames (self , caption = "Select a file to compare" , directory = "./dump" , initialFilter = "All Files (*)" )
214- if file1or2 == "file1" :
215- self .file1 = "" if len (file ) == 0 else file [0 ]
216- if self .file1 :
217- self .diff_dialog_ui .textEdit .setText (self .file1 )
218- elif file1or2 == "file2" :
219- self .file2 = "" if len (file ) == 0 else file [0 ]
220- if self .file2 :
221- self .diff_dialog_ui .textEdit_2 .setText (self .file2 )
221+ def file_dropped_sig_func (self , sig : list ):
222+ if sig [0 ] == "file1" :
223+ self .file1 = sig [1 ]
224+ elif sig [0 ] == "file2" :
225+ self .file2 = sig [1 ]
222226
223227 if self .file1 and self .file2 :
224228 self .diff_dialog_ui .doDiffBtn .setEnabled (True )
225229
226230 @pyqtSlot (str )
227- def binary_compare_finished_sig_func (self , is_finished : str ):
228- if is_finished == "size" :
231+ def binary_compare_finished_sig_func (self , sig : str ):
232+ if sig == "start" :
233+ self .binary_diff_result_window .show ()
234+ elif sig == "size" :
229235 self .statusBar .showMessage ("\t The sizes of the two files are different" , 5000 )
230- elif is_finished == "identical" :
231- self .statusBar .showMessage ("\t Two files are identical" , 5000 )
232- elif is_finished == "finished" :
236+ elif sig == "identical" :
237+ if self .checked_sections and len (self .checked_sections ) == 1 and self .checked_sections [0 ]['name' ] != 'All' :
238+ self .statusBar .showMessage (f"\t { self .checked_sections [0 ]['name' ]} is identical" , 5000 )
239+ elif self .checked_sections and len (self .checked_sections ) > 1 :
240+ self .statusBar .showMessage (f"\t Sections are identical" , 5000 )
241+ else :
242+ self .statusBar .showMessage ("\t Two files are identical" , 5000 )
243+ elif sig == "cannot read" :
244+ self .statusBar .showMessage ("\t Cannot read file contents" , 5000 )
245+ self .binary_diff_result_window .close ()
246+ self .diff_dialog .show ()
247+ elif sig == "finished" :
233248 self .process_diff_result ()
234249
235250 @pyqtSlot (str )
@@ -249,12 +264,99 @@ def process_diff_finished_sig_func(self):
249264 self .diff_result = self .binary_diff_result_ui .binaryDiffResultView .toPlainText ()
250265 self .statusBar .showMessage ("\t Binary diff is done!" , 5000 )
251266
252- def process_diff_result (self ):
253- self .binary_diff_result_window = QtWidgets .QWidget ()
254- self .binary_diff_result_ui = diff_ui .CompareFilesWindow ()
255- self .binary_diff_result_ui .setupUi (self .binary_diff_result_window )
256- self .binary_diff_result_ui .stopDiffBtn .clicked .connect (self .stop_diff )
267+ def section_checkbox (self , checkbox_name , state ):
268+ if state == Qt .CheckState .Checked .value : # Check
269+ if checkbox_name == 'All' :
270+ self .checked_sections .append ({
271+ "start" : None ,
272+ "size" : None ,
273+ "name" : checkbox_name
274+ })
275+ for checkbox in self .diff_dialog_ui .checkboxes :
276+ if checkbox .text () != 'All' :
277+ checkbox .setChecked (False )
278+ checkbox .setEnabled (False )
279+ else :
280+ for section in self .sections :
281+ if section ['name' ] == checkbox_name :
282+ self .checked_sections .append (section )
283+ break
284+ else : # Uncheck
285+ if checkbox_name == 'All' :
286+ self .checked_sections .clear ()
287+ for checkbox in self .diff_dialog_ui .checkboxes :
288+ if checkbox .text () != 'All' :
289+ checkbox .setEnabled (True )
290+ else :
291+ self .checked_sections = [section for section in self .checked_sections
292+ if section ['name' ] != checkbox_name ]
293+
294+ def select_file (self , file1or2 ):
295+ file , _ = QFileDialog .getOpenFileNames (self , caption = "Select a file to compare" , directory = "./dump" , initialFilter = "All Files (*)" )
296+ if file1or2 == "file1" :
297+ self .file1 = "" if len (file ) == 0 else file [0 ]
298+ if self .file1 :
299+ self .diff_dialog_ui .textEdit .setText (self .file1 )
300+ elif file1or2 == "file2" :
301+ self .file2 = "" if len (file ) == 0 else file [0 ]
302+ if self .file2 :
303+ self .diff_dialog_ui .textEdit_2 .setText (self .file2 )
257304
305+ if self .file1 and self .file2 :
306+ self .diff_dialog_ui .doDiffBtn .setEnabled (True )
307+ while self .diff_dialog_ui .checkboxGridLayout .count ():
308+ item = self .diff_dialog_ui .checkboxGridLayout .takeAt (0 ) # Remove the item at position 0
309+ widget = item .widget () # Get the widget associated with the item
310+ if widget :
311+ widget .deleteLater () # Schedule the widget for deletion
312+
313+ r2 = r2pipe .open (self .file1 )
314+ sections_info = r2 .cmdj ("iSj" )
315+ self .sections = [
316+ {
317+ "start" : section ["paddr" ],
318+ "size" : section ["size" ],
319+ "name" : section ["name" ]
320+ }
321+ for section in sections_info
322+ ]
323+
324+ section_found_count = 0
325+ max_columns = 4 # Limit the number of checkboxes per row
326+ row , col = 0 , 0
327+ for section in self .sections :
328+ if section ['name' ]:
329+ section_found_count += 1
330+ if section_found_count == 1 :
331+ label = QtWidgets .QLabel ("Sections:" )
332+ self .diff_dialog_ui .checkboxGridLayout .addWidget (label , row , col )
333+ row += 1
334+
335+ # print(f"Name: {section['name']}, Start: {hex(section['start'])}, Size: {section['size']}")
336+ if section_found_count == 1 :
337+ section_checkbox = QtWidgets .QCheckBox ("All" , self .diff_dialog )
338+ section_checkbox .setObjectName (f"checkbox_all" )
339+ section_checkbox .setChecked (True )
340+ self .checked_sections .append ({
341+ "start" : None ,
342+ "size" : None ,
343+ "name" : "All"
344+ })
345+ else :
346+ section_checkbox = QtWidgets .QCheckBox (section ['name' ], self .diff_dialog )
347+ section_checkbox .setObjectName (f"checkbox_{ section ['name' ]} " )
348+ section_checkbox .setChecked (False )
349+ section_checkbox .setEnabled (False )
350+ section_checkbox .stateChanged .connect (lambda state , cb = section_checkbox :
351+ self .section_checkbox (cb .text (), state ))
352+ self .diff_dialog_ui .checkboxes .append (section_checkbox )
353+ self .diff_dialog_ui .checkboxGridLayout .addWidget (section_checkbox , row , col )
354+ col += 1
355+ if col >= max_columns : # Move to the next row if column limit is reached
356+ col = 0
357+ row += 1
358+
359+ def process_diff_result (self ):
258360 formatted_diffs = self .binary_compare_worker .format_diff (16 )
259361 self .binary_compare_worker .quit ()
260362
@@ -265,17 +367,23 @@ def process_diff_result(self):
265367
266368 def stop_diff (self ):
267369 if self .process_diff_result_worker is not None :
268- self .process_diff_result_worker .process_diff_result_signal .disconnect (self .process_diff_result_sig_func )
269- self .process_diff_result_worker .process_diff_finished_signal .disconnect (self .process_diff_finished_sig_func )
270- self .process_diff_result_worker .terminate ()
370+ try :
371+ self .process_diff_result_worker .process_diff_result_signal .disconnect (self .process_diff_result_sig_func )
372+ self .process_diff_result_worker .process_diff_finished_signal .disconnect (self .process_diff_finished_sig_func )
373+ except Exception as e :
374+ print (e )
375+ if self .process_diff_result_worker .isRunning ():
376+ self .process_diff_result_worker .quit ()
271377 self .diff_result = self .binary_diff_result_ui .binaryDiffResultView .toPlainText ()
272378 self .statusBar .showMessage ("\t Binary diff is done!" , 5000 )
273379
274380 def do_diff (self ):
381+ if len (self .checked_sections ) == 0 :
382+ self .statusBar .showMessage ("\t Choose sections to compare" , 5000 )
383+ return
275384 self .diff_dialog .close ()
276-
277385 if self .file1 and self .file2 :
278- self .binary_compare_worker = BinaryCompareWorker (self .file1 , self .file2 )
386+ self .binary_compare_worker = BinaryCompareWorker (self .file1 , self .file2 , self . checked_sections )
279387 self .binary_compare_worker .binary_compare_finished_sig .connect (self .binary_compare_finished_sig_func )
280388 self .binary_compare_worker .start ()
281389 else :
0 commit comments