import tkinter as tk from tkinter import filedialog, messagebox, ttk, scrolledtext import logging import binascii import os import functools import re import struct import traceback import unicodedata from flash_window import FlashWindow from edc15_info_window import EDC15InfoWindow class HexaWorkApp: def __init__(self, master): self.master = master master.title("HexaWork - Byte Inspector") master.geometry("1000x800") # Configure logging logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('hexawork.log', mode='w'), logging.StreamHandler() ] ) self.logger = logging.getLogger(__name__) # Create main frame self.main_frame = ttk.Frame(master) self.main_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True) # File path variable self.file_path = tk.StringVar() # Order Analysis variables self.normal_hex = tk.StringVar() self.normal_dec = tk.StringVar() self.normal_ihex = tk.StringVar() self.normal_idec = tk.StringVar() self.reverse_hex = tk.StringVar() self.reverse_dec = tk.StringVar() self.reverse_ihex = tk.StringVar() self.reverse_idec = tk.StringVar() # Create menu self.create_menu() # File selection self.create_file_selection_section() # Hex view self.create_hex_view_section() # Order Analysis section self.create_order_analysis_section() # Inspector section self.create_inspector_section() # Search section self.create_search_section() # Selected bytes for analysis self.selected_bytes = None def create_menu(self): """Create application menu""" menubar = tk.Menu(self.master) self.master.config(menu=menubar) # File menu file_menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="File", menu=file_menu) file_menu.add_command(label="Open", command=self.select_file) file_menu.add_command(label="Save", command=self.save_edited_file, state=tk.NORMAL) file_menu.add_command(label="Compare Files", command=self.show_compare_dialog) file_menu.add_command(label="Analyze ASCII", command=self.analyze_ascii_patterns, state=tk.NORMAL) file_menu.add_separator() file_menu.add_command(label="Exit", command=self.master.quit) # Flash menu flash_menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="Flash", menu=flash_menu) flash_menu.add_command(label="EDC15", command=self.open_flash_window) def show_compare_dialog(self): """Show dialog to select files for comparison""" try: # Get original file original_file = filedialog.askopenfilename(title="Select Original File") if not original_file: return # Get modified file modified_file = filedialog.askopenfilename(title="Select Modified File") if not modified_file: return self.compare_files(original_file, modified_file) except Exception as e: self.logger.error(f"Error in compare dialog: {str(e)}") messagebox.showerror("Error", str(e)) def compare_files(self, original_path, modified_path): """Compare two binary files and show differences side by side""" try: # Read files with open(original_path, 'rb') as f: original_data = f.read() with open(modified_path, 'rb') as f: modified_data = f.read() # Create comparison window compare_window = tk.Toplevel(self.master) compare_window.title("File Comparison") compare_window.geometry("1600x800") # Main layout main_frame = ttk.Frame(compare_window) main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) content_frame = ttk.Frame(main_frame) content_frame.pack(fill=tk.BOTH, expand=True) # Hex view frame (side by side) hex_frame = ttk.Frame(content_frame) hex_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5) # Original file column original_column = ttk.Frame(hex_frame) original_column.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5) original_frame = ttk.LabelFrame(original_column, text="Original File") original_frame.pack(fill=tk.BOTH, expand=True) # Create text widgets with equal height and width text_width = 50 # Fixed width for both text widgets text_height = 30 # Fixed height for both text widgets original_text = scrolledtext.ScrolledText(original_column, width=text_width, height=text_height, wrap=tk.NONE) original_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # Original Order Analysis original_order_frame = ttk.LabelFrame(original_column, text="Original File Order Analysis") original_order_frame.pack(fill=tk.X, padx=5, pady=5) # Create Order Analysis variables for original file original_normal_hex = tk.StringVar(value='') original_normal_dec = tk.StringVar(value='') original_normal_ihex = tk.StringVar(value='') original_normal_idec = tk.StringVar(value='') # Add these as instance variables to ensure they persist self.original_normal_hex = original_normal_hex self.original_normal_dec = original_normal_dec self.original_normal_ihex = original_normal_ihex self.original_normal_idec = original_normal_idec # Create labels for original file's order analysis original_normal_frame = ttk.Frame(original_order_frame) original_normal_frame.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5, pady=5) analysis_labels = [ ("HEX:", original_normal_hex, 0, 1), ("!HEX:", original_normal_dec, 0, 3), ("DEC:", original_normal_ihex, 1, 1), ("!DEC:", original_normal_idec, 1, 3) ] for label_text, var, row, col in analysis_labels: ttk.Label(original_normal_frame, text=label_text).grid(row=row, column=col-1, sticky=tk.W) ttk.Label(original_normal_frame, textvariable=var).grid(row=row, column=col, sticky=tk.W) # Modified file column (similar structure) modified_column = ttk.Frame(hex_frame) modified_column.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5) modified_frame = ttk.LabelFrame(modified_column, text="Modified File") modified_frame.pack(fill=tk.BOTH, expand=True) # Create text widgets with equal height and width modified_text = scrolledtext.ScrolledText(modified_column, width=text_width, height=text_height, wrap=tk.NONE) modified_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # Configure identical font for both text widgets font = ('Courier', 10) original_text.configure(font=font) modified_text.configure(font=font) # Modified Order Analysis modified_order_frame = ttk.LabelFrame(modified_column, text="Modified File Order Analysis") modified_order_frame.pack(fill=tk.X, padx=5, pady=5) # Create Order Analysis variables for modified file modified_normal_hex = tk.StringVar(value='') modified_normal_dec = tk.StringVar(value='') modified_normal_ihex = tk.StringVar(value='') modified_normal_idec = tk.StringVar(value='') # Add these as instance variables to ensure they persist self.modified_normal_hex = modified_normal_hex self.modified_normal_dec = modified_normal_dec self.modified_normal_ihex = modified_normal_ihex self.modified_normal_idec = modified_normal_idec # Create labels for modified file's order analysis modified_normal_frame = ttk.Frame(modified_order_frame) modified_normal_frame.pack(fill=tk.X, padx=5, pady=5) analysis_labels = [ ("HEX:", modified_normal_hex, 0, 1), ("!HEX:", modified_normal_dec, 0, 3), ("DEC:", modified_normal_ihex, 1, 1), ("!DEC:", modified_normal_idec, 1, 3) ] for label_text, var, row, col in analysis_labels: ttk.Label(modified_normal_frame, text=label_text).grid(row=row, column=col-1, sticky=tk.W, padx=5, pady=2) ttk.Label(modified_normal_frame, textvariable=var, width=15).grid(row=row, column=col, sticky=tk.W, padx=5, pady=2) # Synchronize scrollbars def on_scroll(*args): original_text.yview_moveto(args[0]) modified_text.yview_moveto(args[0]) original_text.vbar.config(command=lambda *args: on_scroll(*args)) modified_text.vbar.config(command=lambda *args: on_scroll(*args)) # Format and insert the data original_text.insert(tk.END, self.format_hex_view(original_data)) modified_text.insert(tk.END, self.format_hex_view(modified_data)) # Differences table (right side) table_frame = ttk.LabelFrame(content_frame, text="Differences") table_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5) columns = ('Offset', 'Original Byte', 'Modified Byte') differences_table = ttk.Treeview(table_frame, columns=columns, show='headings') for col in columns: differences_table.heading(col, text=col) differences_table.column(col, width=100, anchor='center') # Perform order analysis for original file original_analysis = self.perform_order_analysis( original_data, original_normal_hex, original_normal_dec, original_normal_ihex, original_normal_idec, start_index=0, # Start at the beginning num_bytes=2 # Use first 2 bytes ) # Perform order analysis for modified file modified_analysis = self.perform_order_analysis( modified_data, modified_normal_hex, modified_normal_dec, modified_normal_ihex, modified_normal_idec, start_index=0, # Start at the beginning num_bytes=2 # Use first 2 bytes ) # Log the analysis results self.logger.debug(f"Original Analysis: {original_analysis}") self.logger.debug(f"Modified Analysis: {modified_analysis}") # Force update of StringVars and trigger trace callbacks original_normal_hex.trace_add('write', lambda *args: original_normal_hex.get()) original_normal_dec.trace_add('write', lambda *args: original_normal_dec.get()) original_normal_ihex.trace_add('write', lambda *args: original_normal_ihex.get()) original_normal_idec.trace_add('write', lambda *args: original_normal_idec.get()) modified_normal_hex.trace_add('write', lambda *args: modified_normal_hex.get()) modified_normal_dec.trace_add('write', lambda *args: modified_normal_dec.get()) modified_normal_ihex.trace_add('write', lambda *args: modified_normal_ihex.get()) modified_normal_idec.trace_add('write', lambda *args: modified_normal_idec.get()) # Force a manual update of the window compare_window.update_idletasks() # Ensure UI updates compare_window.update() # Create selection tracker for text widgets def create_selection_tracker(text_widget, analysis_vars, data): def on_select(event): try: # Get the selected text and clean it selected_text = text_widget.get(tk.SEL_FIRST, tk.SEL_LAST) # Remove non-hex characters and spaces cleaned_text = ''.join(char for char in selected_text if char in '0123456789ABCDEFabcdef') # Ensure even number of characters for hex conversion if len(cleaned_text) % 2 != 0: cleaned_text = '0' + cleaned_text # Convert to bytes selected_bytes = bytes.fromhex(cleaned_text) # Always set selected_bytes for order analysis self.selected_bytes = selected_bytes # Perform order analysis on selected bytes analysis_result = self.perform_order_analysis( selected_bytes, analysis_vars[0], # hex_var analysis_vars[1], # dec_var analysis_vars[2], # ihex_var analysis_vars[3], # idec_var start_index=0, num_bytes=len(selected_bytes) ) self.logger.debug(f"Selected bytes for {text_widget}: {selected_bytes}") self.logger.debug(f"Analysis result: {analysis_result}") except tk.TclError: # No text selected pass except ValueError as e: self.logger.error(f"Error converting selection to bytes: {e}") # Reset analysis variables if conversion fails analysis_vars[0].set('N/A') analysis_vars[1].set('N/A') analysis_vars[2].set('N/A') analysis_vars[3].set('N/A') except Exception as e: self.logger.error(f"Unexpected error in selection tracking: {e}", exc_info=True) # Use both selection and button release events text_widget.tag_bind(text_widget, '<>', on_select) text_widget.bind('', on_select) # Create separate trackers for original and modified text widgets create_selection_tracker( original_text, [self.original_normal_hex, self.original_normal_dec, self.original_normal_ihex, self.original_normal_idec], original_data ) create_selection_tracker( modified_text, [self.modified_normal_hex, self.modified_normal_dec, self.modified_normal_ihex, self.modified_normal_idec], modified_data ) # Manually trigger initial order analysis for first bytes self.selected_bytes = original_data[:2] self.perform_order_analysis( original_data[:2], self.original_normal_hex, self.original_normal_dec, self.original_normal_ihex, self.original_normal_idec ) # Manually trigger initial order analysis for modified file self.selected_bytes = modified_data[:2] self.perform_order_analysis( modified_data[:2], self.modified_normal_hex, self.modified_normal_dec, self.modified_normal_ihex, self.modified_normal_idec ) # Find and highlight differences differences = self.find_differences(original_data, modified_data) original_text.tag_configure('different', background='#FFE6E6') modified_text.tag_configure('different', background='#E6FFE6') difference_list = [] for offset in differences: line = offset // 16 col = (offset % 16) * 3 # Adjust highlight position to align exactly with bytes # Format is: "0000: XX XX XX..." so we need to account for "0000: " (6 chars) hex_start = 6 # Position after the offset and colon (0000:_) start_pos = f"{line + 1}.{hex_start + col}" end_pos = f"{line + 1}.{hex_start + col + 2}" original_text.tag_add('different', start_pos, end_pos) modified_text.tag_add('different', start_pos, end_pos) original_byte = original_data[offset] if offset < len(original_data) else 'N/A' modified_byte = modified_data[offset] if offset < len(modified_data) else 'N/A' difference_list.append({ 'offset': offset, 'original_byte': f'{original_byte:02X}', 'modified_byte': f'{modified_byte:02X}' }) # Insert differences into table for diff in difference_list: differences_table.insert('', 'end', values=( f'0x{diff["offset"]:04X}', diff['original_byte'], diff['modified_byte'] )) differences_table.pack(fill=tk.BOTH, expand=True) # Make text widgets read-only original_text.configure(state='disabled') modified_text.configure(state='disabled') # Remove the bottom bar compare_window.overrideredirect(False) compare_window.resizable(True, True) except Exception as e: self.logger.error(f"Error comparing files: {e}") messagebox.showerror("Error", f"Could not compare files: {e}") def find_differences(self, original_data, modified_data): """Find unique byte differences between two files""" differences = set() min_length = min(len(original_data), len(modified_data)) # Compare bytes for i in range(min_length): if original_data[i] != modified_data[i]: differences.add(i) # Add remaining bytes as differences if files have different lengths if len(original_data) > min_length: differences.update(range(min_length, len(original_data))) elif len(modified_data) > min_length: differences.update(range(min_length, len(modified_data))) return differences def navigate_differences(self, direction): """Navigate through differences in the comparison view""" # Implementation for navigating through differences pass def create_file_selection_section(self): file_frame = ttk.LabelFrame(self.main_frame, text="File Selection") file_frame.pack(fill=tk.X, pady=5) ttk.Label(file_frame, textvariable=self.file_path).pack(side=tk.LEFT, padx=5) ttk.Button(file_frame, text="Select File", command=self.select_file).pack(side=tk.RIGHT, padx=5) def open_flash_window(self): FlashWindow(self.master, self.master) def create_hex_view_section(self): """Create hex view section with scrollable text widget""" # Hex view frame hex_frame = ttk.LabelFrame(self.main_frame, text="Hex View") hex_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True) # Scrolled text widget for hex view self.hex_text = tk.Text(hex_frame, wrap=tk.NONE, font=('Courier', 10)) self.hex_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # Add vertical scrollbar v_scrollbar = ttk.Scrollbar(hex_frame, orient=tk.VERTICAL, command=self.hex_text.yview) v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.hex_text.configure(yscrollcommand=v_scrollbar.set) # Add horizontal scrollbar h_scrollbar = ttk.Scrollbar(hex_frame, orient=tk.HORIZONTAL, command=self.hex_text.xview) h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X) self.hex_text.configure(xscrollcommand=h_scrollbar.set) # Bind key events for custom processing self.hex_text.bind('', self.on_hex_text_key) # Bind selection events self.hex_text.bind('<>', self.on_text_select) self.hex_text.bind('', self.on_text_select) self.hex_text.bind('', self.on_text_select) def on_hex_text_key(self, event): """Custom key event handler for hex text editing""" # Allow navigation and editing keys if event.keysym in ['Left', 'Right', 'Up', 'Down', 'Home', 'End', 'Prior', 'Next']: return # Allow backspace and delete if event.keysym in ['BackSpace', 'Delete']: self.master.after(10, self.process_hex_text_edit) return # Only process for printable hex characters if event.char and event.char not in '0123456789ABCDEFabcdef': return 'break' # Delay processing to allow default key handling self.master.after(10, self.process_hex_text_edit) def process_hex_text_edit(self): """Process hex text edits to maintain formatting""" try: # Get entire text content full_text = self.hex_text.get('1.0', tk.END) # Get current cursor position cursor_index = self.hex_text.index(tk.INSERT) # Log current state for debugging self.logger.debug(f"Current cursor index: {cursor_index}") self.logger.debug(f"Full text length: {len(full_text)}") # Split text into lines lines = full_text.split('\n') # Process each line to extract hex values clean_hex_values = [] for line in lines: # Extract hex values from each line (between ':' and ASCII representation) if ':' in line: hex_part = line.split(':', 1)[1].split(' ', 1)[0] # Remove spaces and collect hex values line_hex = hex_part.replace(' ', '') if line_hex: clean_hex_values.append(line_hex) # Join all hex values clean_text = ''.join(clean_hex_values) # Reformat the text formatted_text = self.reformat_hex_text(clean_text) # Update text widget self.hex_text.delete('1.0', tk.END) self.hex_text.insert('1.0', formatted_text) # Restore cursor position total_lines = len(formatted_text.split('\n')) cursor_line, cursor_column = map(int, cursor_index.split('.')) # Ensure cursor line is within new text if cursor_line > total_lines: cursor_line = total_lines # Get the last line's length last_line = formatted_text.split('\n')[-1] last_line_hex = last_line.split(':', 1)[1].split(' ', 1)[0].replace(' ', '') # Adjust column if it exceeds line length if cursor_column > len(last_line_hex) + 1: cursor_column = len(last_line_hex) + 1 # Set cursor position new_cursor_index = f'{cursor_line}.{cursor_column}' self.logger.debug(f"Restored cursor index: {new_cursor_index}") self.hex_text.mark_set(tk.INSERT, new_cursor_index) self.hex_text.see(new_cursor_index) except Exception as e: self.logger.error(f"Error processing hex text edit: {str(e)}") def reformat_hex_text(self, hex_string): """Reformat hex string to maintain original view""" # Ensure hex string length is even if len(hex_string) % 2 != 0: hex_string = hex_string + '0' # Convert to bytes for consistent formatting try: data = bytes.fromhex(hex_string) return self.format_hex_view(data) except ValueError: # Fallback if conversion fails return hex_string def create_order_analysis_section(self): order_frame = ttk.LabelFrame(self.main_frame, text="Order Analysis") order_frame.pack(fill=tk.X, pady=5) # Normal Order normal_frame = ttk.LabelFrame(order_frame, text="Normal Order") normal_frame.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5, pady=5) ttk.Label(normal_frame, text="HEX:").grid(row=0, column=0, sticky=tk.W) ttk.Label(normal_frame, textvariable=self.normal_hex).grid(row=0, column=1, sticky=tk.W) ttk.Label(normal_frame, text="DEC:").grid(row=1, column=0, sticky=tk.W) ttk.Label(normal_frame, textvariable=self.normal_dec).grid(row=1, column=1, sticky=tk.W) ttk.Label(normal_frame, text="!HEX:").grid(row=0, column=2, sticky=tk.W) ttk.Label(normal_frame, textvariable=self.normal_ihex).grid(row=0, column=3, sticky=tk.W) ttk.Label(normal_frame, text="!DEC:").grid(row=1, column=2, sticky=tk.W) ttk.Label(normal_frame, textvariable=self.normal_idec).grid(row=1, column=3, sticky=tk.W) # Reverse Order reverse_frame = ttk.LabelFrame(order_frame, text="Reverse Order") reverse_frame.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5, pady=5) ttk.Label(reverse_frame, text="HEX:").grid(row=0, column=0, sticky=tk.W) ttk.Label(reverse_frame, textvariable=self.reverse_hex).grid(row=0, column=1, sticky=tk.W) ttk.Label(reverse_frame, text="DEC:").grid(row=1, column=0, sticky=tk.W) ttk.Label(reverse_frame, textvariable=self.reverse_dec).grid(row=1, column=1, sticky=tk.W) ttk.Label(reverse_frame, text="!HEX:").grid(row=0, column=2, sticky=tk.W) ttk.Label(reverse_frame, textvariable=self.reverse_ihex).grid(row=0, column=3, sticky=tk.W) ttk.Label(reverse_frame, text="!DEC:").grid(row=1, column=2, sticky=tk.W) ttk.Label(reverse_frame, textvariable=self.reverse_idec).grid(row=1, column=3, sticky=tk.W) def create_inspector_section(self): inspector_frame = ttk.LabelFrame(self.main_frame, text="Inspector") inspector_frame.pack(fill=tk.X, pady=5) # Create labels for various representations self.checksum_sum = tk.StringVar() self.checksum_xor = tk.StringVar() self.checksum_16 = tk.StringVar() ttk.Label(inspector_frame, text="Checksum Sum:").grid(row=0, column=0, sticky=tk.W) ttk.Label(inspector_frame, textvariable=self.checksum_sum).grid(row=0, column=1, sticky=tk.W) ttk.Label(inspector_frame, text="Checksum XOR:").grid(row=1, column=0, sticky=tk.W) ttk.Label(inspector_frame, textvariable=self.checksum_xor).grid(row=1, column=1, sticky=tk.W) ttk.Label(inspector_frame, text="Checksum 16-bit:").grid(row=2, column=0, sticky=tk.W) ttk.Label(inspector_frame, textvariable=self.checksum_16).grid(row=2, column=1, sticky=tk.W) def create_search_section(self): search_frame = ttk.LabelFrame(self.main_frame, text="Search") search_frame.pack(fill=tk.X, pady=5) ttk.Label(search_frame, text="Search Value:").grid(row=0, column=0) self.search_entry = ttk.Entry(search_frame, width=30) self.search_entry.grid(row=0, column=1, padx=5) ttk.Button(search_frame, text="Search", command=self.perform_search).grid(row=0, column=2) ttk.Button(search_frame, text="Next Result", command=self.next_result).grid(row=0, column=3) ttk.Button(search_frame, text="Clear", command=self.clear_search).grid(row=0, column=4) def select_file(self): self.logger.info("Opening file dialog") file_path = filedialog.askopenfilename( filetypes=[("Binary files", "*.bin"), ("All files", "*.*")] ) if file_path: try: # Check if it's an EDC15 file filename = os.path.basename(file_path) self.logger.info(f"Selected file: {filename}") # Check for EDC15 in filename (case insensitive) if "edc15" in filename.lower() or "edc_15" in filename.lower(): self.logger.info("EDC15 file detected, opening info window") try: info_window = EDC15InfoWindow(self.master, filename) info_window.transient(self.master) info_window.grab_set() self.master.wait_window(info_window) self.logger.info("EDC15 info window closed") except Exception as e: self.logger.error(f"Error creating EDC15 info window: {str(e)}") self.logger.error(traceback.format_exc()) else: self.logger.info("Not an EDC15 file") with open(file_path, 'rb') as file: data = file.read() self.file_path.set(file_path) self.inspect_file(data) except Exception as e: self.logger.error(f"Error opening file: {e}") messagebox.showerror("Error", f"Could not open file: {e}") def inspect_file(self, data): """Inspect and display contents of a binary file""" try: # Clear previous content self.hex_text.config(state=tk.NORMAL) self.hex_text.delete('1.0', tk.END) # Format and display hex view hex_view = self.format_hex_view(data) self.hex_text.insert(tk.END, hex_view) # Make text editable and highlight in light red self.hex_text.tag_config('editable', background='#FFE6E6') self.hex_text.tag_add('editable', '1.0', tk.END) # Configure text widget for editing self.hex_text.config(state=tk.NORMAL) # Calculate and update checksums checksums = self.calculate_checksums(data) self.checksum_sum.set(f"{checksums['sum']:02X}") self.checksum_xor.set(f"{checksums['xor']:02X}") self.checksum_16.set(f"{checksums['16bit']:04X}") # Store original file path for potential saving self.current_file_path = self.file_path.get() # Update window title self.master.title(f"HexaWork - {os.path.basename(self.current_file_path)}") except Exception as e: self.logger.error(f"Error inspecting file: {e}") messagebox.showerror("Error", f"Could not inspect file: {e}") def save_edited_file(self): """Save the edited binary file""" if not hasattr(self, 'current_file_path') or not self.current_file_path: messagebox.showerror("Error", "No file is currently open") return try: # Get the original file size original_size = os.path.getsize(self.current_file_path) # Get the edited text and convert to bytes edited_text = self.hex_text.get('1.0', tk.END).strip() # Remove spaces and convert to bytes hex_values = edited_text.replace('\n', '').replace(' ', '') # Validate hex string if not all(c in '0123456789ABCDEFabcdef' for c in hex_values): messagebox.showerror("Error", "Invalid hex characters") return # Convert to bytes edited_bytes = bytes.fromhex(hex_values) # Check file size change if len(edited_bytes) != original_size: # Ask user about file size change response = messagebox.askyesno( "File Size Change", f"Warning: File size will change!\n\n" f"Original Size: {original_size} bytes\n" f"New Size: {len(edited_bytes)} bytes\n\n" "Do you want to continue saving?" ) if not response: return # Save the file with open(self.current_file_path, 'wb') as f: f.write(edited_bytes) messagebox.showinfo("Success", "File saved successfully") # Update the current file view after saving self.inspect_file(edited_bytes) except Exception as e: self.logger.error(f"Error saving file: {e}") messagebox.showerror("Error", f"Could not save file: {e}") def format_hex_view(self, data, bytes_per_row=16): """Format binary data into hex view with offset, hex values, and ASCII representation""" hex_view = [] # Determine the maximum length of the offset for consistent formatting max_offset = len(f'{len(data):X}') for i in range(0, len(data), bytes_per_row): # Get the current chunk of bytes chunk = data[i:i+bytes_per_row] # Format offset (right-aligned with leading zeros) offset = f'{i:0{max_offset}X}' # Format hex values (2 digits per byte, space-separated) hex_values = ' '.join(f'{byte:02X}' for byte in chunk) # Pad hex values to ensure consistent width hex_values = hex_values.ljust(bytes_per_row * 3 - 1) # Format ASCII representation ascii_repr = ''.join(chr(byte) if 32 <= byte <= 126 else '.' for byte in chunk) # Combine all parts line = f'{offset}: {hex_values} {ascii_repr}' hex_view.append(line) return '\n'.join(hex_view) def calculate_checksums(self, data): """Calculate various checksums for the given data""" # Ensure data is converted to bytes if it's not already if not isinstance(data, bytes): data = bytes(data) # Sum checksum (1 byte) sum_checksum = sum(data) & 0xFF # XOR checksum (1 byte) xor_checksum = 0 for byte in data: xor_checksum ^= byte xor_checksum &= 0xFF # 16-bit checksum bit16_checksum = 0 for i in range(0, len(data), 2): if i + 1 < len(data): # Combine two bytes into 16-bit value value = (data[i] << 8) | data[i + 1] else: # Handle odd number of bytes value = (data[i] << 8) bit16_checksum = (bit16_checksum + value) & 0xFFFF return { 'sum': sum_checksum, 'xor': xor_checksum, '16bit': bit16_checksum } def perform_search(self): search_value = self.search_entry.get() if not search_value: return # Placeholder search implementation hex_content = self.hex_text.get('1.0', tk.END) results = [m.start() for m in re.finditer(re.escape(search_value), hex_content)] if results: self.current_search_results = results self.current_search_index = 0 self.highlight_current_result() else: messagebox.showinfo("Search", "No results found.") def next_result(self): if not hasattr(self, 'current_search_results') or not self.current_search_results: return self.current_search_index = (self.current_search_index + 1) % len(self.current_search_results) self.highlight_current_result() def highlight_current_result(self): if not hasattr(self, 'current_search_results'): return result_pos = self.current_search_results[self.current_search_index] self.hex_text.tag_remove('search_highlight', '1.0', tk.END) start_index = self.hex_text.index(f'1.0 + {result_pos}c') end_index = self.hex_text.index(f'{start_index} + {len(self.search_entry.get())}c') self.hex_text.tag_add('search_highlight', start_index, end_index) self.hex_text.tag_config('search_highlight', background='yellow') self.hex_text.see(start_index) def clear_search(self): self.search_entry.delete(0, tk.END) self.hex_text.tag_remove('search_highlight', '1.0', tk.END) if hasattr(self, 'current_search_results'): del self.current_search_results self.current_search_index = -1 def on_text_select(self, event=None): try: # Debug - print full widget content and selection self.debug_text_widget_content() # Obter widget de texto text_widget = event.widget if event else self.hex_text # Log de depuração self.logger.debug(f"Text selection event triggered on {text_widget}") # Obter texto selecionado try: selected_text = text_widget.get(tk.SEL_FIRST, tk.SEL_LAST) self.logger.debug(f"Selected text: {selected_text}") except tk.TclError: self.logger.warning("No text selected") self.clear_order_analysis() # Clear checksums when no selection self.checksum_sum.set("") self.checksum_xor.set("") self.checksum_16.set("") return # Remover espaços e caracteres não hexadecimais hex_text = ''.join(c for c in selected_text.upper() if c in '0123456789ABCDEF') self.logger.debug(f"Cleaned hex text: {hex_text}") # Verificar se o texto é válido if not hex_text: self.logger.warning("No valid hex characters found") self.clear_order_analysis() # Clear checksums when invalid selection self.checksum_sum.set("") self.checksum_xor.set("") self.checksum_16.set("") return # Garantir que o número de caracteres seja par if len(hex_text) % 2 != 0: hex_text = hex_text + '0' self.logger.debug(f"Padded hex text: {hex_text}") # Converter para bytes try: self.selected_bytes = bytes.fromhex(hex_text) self.logger.info(f"Selected bytes: {self.selected_bytes}") except ValueError as e: self.logger.error(f"Error converting to bytes: {e}") self.clear_order_analysis() # Clear checksums on error self.checksum_sum.set("") self.checksum_xor.set("") self.checksum_16.set("") return # Atualizar análise de ordem self.update_order_analysis() # Calculate and update checksums checksums = self.calculate_checksums(self.selected_bytes) self.checksum_sum.set(f"{checksums['sum']:02X}") self.checksum_xor.set(f"{checksums['xor']:02X}") self.checksum_16.set(f"{checksums['16bit']:04X}") except Exception as e: self.logger.error(f"Unexpected error in on_text_select: {e}", exc_info=True) self.selected_bytes = None self.clear_order_analysis() # Clear checksums on error self.checksum_sum.set("") self.checksum_xor.set("") self.checksum_16.set("") def clear_order_analysis(self): """Clear all order analysis values""" # Normal Order self.normal_hex.set("") self.normal_dec.set("") self.normal_ihex.set("") self.normal_idec.set("") # Reverse Order self.reverse_hex.set("") self.reverse_dec.set("") self.reverse_ihex.set("") self.reverse_idec.set("") # Reset selected bytes self.selected_bytes = None self.logger.debug("Order analysis values cleared") def update_order_analysis(self): if not self.selected_bytes: self.logger.info("No bytes selected for order analysis") return try: # Normal Order Analysis normal_hex_str = ''.join(f'{byte:02X}' for byte in self.selected_bytes) normal_dec = int(normal_hex_str, 16) # Log calculated values self.logger.debug(f"Normal HEX: {normal_hex_str}") self.logger.debug(f"Normal DEC: {normal_dec}") self.logger.debug(f"Inverted HEX: {(~normal_dec & 0xFFFF):04X}") self.logger.debug(f"Inverted DEC: {~normal_dec & 0xFFFF}") # Force update of StringVars self.normal_hex.set(normal_hex_str) self.normal_dec.set(str(normal_dec)) self.normal_ihex.set(f'{(~normal_dec & 0xFFFF):04X}') self.normal_idec.set(str(~normal_dec & 0xFFFF)) # Reverse Order Analysis reverse_hex_str = ''.join(f'{byte:02X}' for byte in reversed(self.selected_bytes)) reverse_dec = int(reverse_hex_str, 16) # Log calculated values self.logger.debug(f"Reverse HEX: {reverse_hex_str}") self.logger.debug(f"Reverse DEC: {reverse_dec}") self.logger.debug(f"Inverted HEX: {(~reverse_dec & 0xFFFF):04X}") self.logger.debug(f"Inverted DEC: {~reverse_dec & 0xFFFF}") # Force update of StringVars self.reverse_hex.set(reverse_hex_str) self.reverse_dec.set(str(reverse_dec)) self.reverse_ihex.set(f'{(~reverse_dec & 0xFFFF):04X}') self.reverse_idec.set(str(~reverse_dec & 0xFFFF)) self.logger.info(f"Order analysis updated: {normal_hex_str} / {reverse_hex_str}") except Exception as e: self.logger.error(f"Error updating order analysis: {e}", exc_info=True) self.clear_order_analysis() def analyze_ascii_patterns(self): """Analyze ASCII patterns in the current file""" if not hasattr(self, 'current_file_path') or not self.current_file_path: messagebox.showerror("Error", "No file is currently open") return try: # Read file in binary mode with open(self.current_file_path, 'rb') as f: data = f.read() # Convert to ASCII string, replacing non-printable characters ascii_text = ''.join(chr(byte) if 32 <= byte <= 126 else ' ' for byte in data) # Patterns to search patterns = { 'Possible Software Number': r'\b\d{10,11}\b', 'VIN (Vehicle Identification Number)': r'\b[A-Z0-9]{17}\b', } # Collect results results = {} for pattern_name, pattern in patterns.items(): matches = re.findall(pattern, ascii_text) if matches: # Count unique occurrences unique_matches = {} for match in matches: unique_matches[match] = unique_matches.get(match, 0) + 1 results[pattern_name] = unique_matches # Additional custom pattern searches with minimum 3 character requirement custom_patterns = [ ('Potential Codes', r'\b[A-Z]{3,}\b'), # 3 or more letter codes ('Long Numbers', r'\b\d{10,}\b'), ('Possible Pin Code', r'\b[A-Z0-9]{4}\b') # 4-character alphanumeric codes ] for pattern_name, pattern in custom_patterns: matches = re.findall(pattern, ascii_text) if matches: # Count unique occurrences unique_matches = {} for match in matches: unique_matches[match] = unique_matches.get(match, 0) + 1 results[pattern_name] = unique_matches # Create results window if results: self.show_ascii_analysis_results(results) else: messagebox.showinfo("ASCII Analysis", "No interesting patterns found.") except Exception as e: self.logger.error(f"Error in ASCII analysis: {e}") messagebox.showerror("Error", f"Could not perform ASCII analysis: {e}") def show_ascii_analysis_results(self, results): """Display ASCII analysis results in a popup window""" # Create top-level window result_window = tk.Toplevel(self.master) result_window.title("ASCII Pattern Analysis") result_window.geometry("600x400") # Create text widget to display results results_text = tk.Text(result_window, wrap=tk.WORD, font=('Courier', 10)) results_text.pack(padx=10, pady=10, fill=tk.BOTH, expand=True) # Add scrollbar scrollbar = ttk.Scrollbar(result_window, command=results_text.yview) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) results_text.configure(yscrollcommand=scrollbar.set) # Format and insert results results_text.insert(tk.END, "ASCII Pattern Analysis Results:\n\n") for category, matches in results.items(): results_text.insert(tk.END, f"{category}:\n") for match, count in matches.items(): results_text.insert(tk.END, f" - {match} (Occurrences: {count})\n") results_text.insert(tk.END, "\n") # Add a close button close_button = ttk.Button(result_window, text="Close", command=result_window.destroy) close_button.pack(pady=10) def debug_text_widget_content(self): """Debug method to print the entire content of the hex text widget""" try: # Get the full content of the text widget full_content = self.hex_text.get('1.0', tk.END).strip() self.logger.debug(f"Full hex text widget content:\n{full_content}") # Check if there's a selection try: selected_text = self.hex_text.get(tk.SEL_FIRST, tk.SEL_LAST) self.logger.debug(f"Currently selected text:\n{selected_text}") except tk.TclError: self.logger.debug("No text currently selected") except Exception as e: self.logger.error(f"Error in debug_text_widget_content: {e}") def perform_order_analysis(self, data, hex_var, dec_var, ihex_var, idec_var, start_index=0, num_bytes=2): try: # Log input data for debugging self.logger.debug(f"Order Analysis - Input Data: {data}") self.logger.debug(f"Input Data Length: {len(data)}") # Ensure we have enough bytes, pad with zeros if needed selected_bytes = list(data[start_index:start_index+num_bytes]) selected_bytes += [0] * (num_bytes - len(selected_bytes)) # Log selected bytes for debugging self.logger.debug(f"Selected Bytes: {selected_bytes}") # Ensure at least 2 bytes for proper analysis while len(selected_bytes) < 2: selected_bytes.append(0) # Normal Order normal_hex = ''.join(f'{byte:02X}' for byte in selected_bytes) normal_dec = int(normal_hex, 16) # Log calculated values self.logger.debug(f"Normal HEX: {normal_hex}") self.logger.debug(f"Normal DEC: {normal_dec}") self.logger.debug(f"Inverted HEX: {(~normal_dec & 0xFFFF):04X}") self.logger.debug(f"Inverted DEC: {~normal_dec & 0xFFFF}") # Force update of StringVars hex_var.set(normal_hex) dec_var.set(str(normal_dec)) ihex_var.set(f'{(~normal_dec & 0xFFFF):04X}') idec_var.set(str(~normal_dec & 0xFFFF)) return { 'hex': normal_hex, 'dec': str(normal_dec), 'ihex': f'{(~normal_dec & 0xFFFF):04X}', 'idec': str(~normal_dec & 0xFFFF) } except Exception as e: # Log full error details self.logger.error(f"Error in order analysis: {e}", exc_info=True) # Set default values if analysis fails hex_var.set('N/A') dec_var.set('N/A') ihex_var.set('N/A') idec_var.set('N/A') return { 'hex': 'N/A', 'dec': 'N/A', 'ihex': 'N/A', 'idec': 'N/A' } def open_edc15_info(self, filename, binary_data): # Open EDC15 Info Window edc15_info_window = EDC15InfoWindow(self, filename, binary_data) edc15_info_window.wait_window() # Check if project was created or window was closed if not edc15_info_window.project_created or edc15_info_window.window_closed: # If project not created or window closed, return to Flash Window self.show_flash_window() def main(): root = tk.Tk() app = HexaWorkApp(root) root.mainloop() if __name__ == "__main__": main()