diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6d30f2 --- /dev/null +++ b/README.md @@ -0,0 +1,297 @@ +# HexaWork - Byte Inspector + +## Overview +HexaWork is a Python Tkinter application for binary file inspection and analysis. It provides a hexadecimal viewer and advanced byte analysis capabilities. + +## Features + +### 1. Hexadecimal View +- Three-column display: offset, hexadecimal values, and ASCII representation +- Uppercase hexadecimal values for better readability +- Monospace font for perfect alignment +- Horizontal and vertical scrollbars + +### 2. Byte Analysis +#### Normal Order +- HEX: Hexadecimal representation of selected bytes +- DEC: Corresponding decimal value +- !HEX: Bitwise complement in hexadecimal +- !DEC: Bitwise complement in decimal + +#### Reverse Order +- HEX: Hexadecimal representation of bytes in reverse order +- DEC: Decimal value of reverse order +- !HEX: Bitwise complement in hexadecimal +- !DEC: Bitwise complement in decimal + +### 3. Flexible Selection +- Direct selection of hexadecimal values +- Full line selection including offset and ASCII +- Automatic analysis update on selection + +### 4. ASCII Pattern Analysis +- Comprehensive file-wide ASCII text scanning +- Detects various patterns: + * Phone numbers + * Short letter sequences + * Alphanumeric codes + * Potential 3-letter abbreviations +- Interactive results popup +- Supports multiple pattern types + +### 5. Advanced ASCII Pattern Recognition +- Comprehensive file-wide text scanning +- Multiple pattern detection: + * Possible Software Numbers (10-11 digits) + * Vehicle Identification Numbers (VIN) + * Potential Code Sequences + * Possible PIN Codes +- Unique pattern occurrence counting +- Interactive results display +- Supports complex alphanumeric pattern matching + +### 6. Pattern Analysis Features +- Detailed pattern identification: + * Detects 3+ letter codes + * Finds long numeric sequences + * Highlights repeated patterns +- Occurrence frequency tracking +- User-friendly results popup +- Flexible pattern recognition + +## Recent Updates and Enhancements + +### Logging System +- Created centralized logging mechanism in `logger.py` +- Logs saved in timestamped files in `logs/` directory +- Provides detailed error tracking and information logging +- Supports both console and file logging + +### Binary Graph Visualization +- Replaced Matplotlib with Plotly for interactive graphing +- Added `tkinterweb` support for rendering interactive graphs +- Enhanced scrolling capabilities for large binary files +- Improved error handling and data validation + +### Error Handling +- Comprehensive error management across all modules +- User-friendly error messages +- Detailed logging for debugging purposes + +### Performance Improvements +- More robust data type handling +- Optimized graph rendering +- Improved scrolling mechanisms + +### New Dependencies +- Added `plotly` for interactive graphing +- Added `tkinterweb` for HTML-based graph rendering + +### Installation +```bash +pip install -r requirements.txt +``` + +### Known Limitations +- Initial graph view limited to 512 data points +- Requires Python 3.12+ +- Windows platform recommended + +## Updates (December 6, 2023) + +### EDC15 Info Window Enhancements +- **Hardware Number Detection** + - Automatically detects and displays 9-digit hardware numbers starting with prefixes (0281, 0265, 0255, 0275, 0235, 0285) + - Additional hardware numbers found are displayed in the comments section + +- **Software Number Detection** + - Automatically finds and displays 9-digit software numbers starting with "103" + - Additional software numbers found are shown in the comments section + - Duplicate numbers are filtered out to avoid redundancy + +- **Interface Improvements** + - Reorganized car information layout: + - Left side: Vertical alignment of all fields (Brand, Model, Engine, HP, KW, Year, VIN, Transmission) + - Right side: Comments section with scrollbar + - Replaced Save/Cancel buttons with: + - Create Project button (left-aligned) + - Cancel button (right-aligned) + - Improved field organization and spacing for better user experience + - Comments now show only unique additional hardware/software numbers + +### New Features +- **Project Creation** + - Added new "Create Project" functionality + - Integrated with existing ECU information system + - Improved data validation and error handling + +### Technical Updates +- Enhanced error logging for better debugging +- Improved memory management for binary file handling +- Updated UI components for better responsiveness + +## Updates (December 20, 2023) + +### Project Creation Workflow Enhancement +- Implemented strict project creation workflow +- Users must create a project before opening a binary file +- Added validation to ensure all required fields are filled +- Improved user experience by guiding project creation process + +#### New Project Creation Rules +- "New Project" button in Flash Window initiates file selection +- EDC15 Info Window appears with mandatory fields +- Project can only be created after filling all required information +- Closing the info window without creating a project returns to the main screen +- Prevents accidental file opening without proper project context + +#### Validation Improvements +- Comprehensive field validation in project creation +- Prevents project creation with incomplete information +- User-friendly warning messages for missing fields +- Enhanced data integrity and workflow control + +### Technical Implementation Details +- Added `project_created` flag in `EDC15InfoWindow` +- Implemented `window_closed` flag to track user interactions +- Updated `open_file()` method in `FlashWindow` +- Modified main application flow to enforce project creation + +### Benefits +- Improved data tracking and project management +- Prevents incomplete or accidental file processing +- Provides clear guidance for users during project creation +- Enhances overall application reliability + +## Troubleshooting +- If you encounter any import errors, ensure all dependencies are installed +- Check log files in the `logs/` directory for detailed error information + +## Installation + +1. Requirements: + - Python 3.7 or higher + - Tkinter (usually included with Python) + +2. Install dependencies: +```bash +pip install -r requirements.txt +``` + +## Usage + +1. Run the program: +```bash +python main.py +``` + +2. Features: + - Click "Select File" to choose a binary file + - Select bytes in the hex viewer (using mouse or keyboard) + - View automatic analysis in Normal Order and Reverse Order panels + - Use scrollbars to navigate large files + +## Examples + +### Byte Analysis +If you select bytes "4E 31": +- Normal Order: + * HEX: 4E31 + * DEC: 20017 + * !HEX: [complement] + * !DEC: [decimal complement] + +- Reverse Order: + * HEX: 314E + * DEC: 12622 + * !HEX: [complement] + * !DEC: [decimal complement] + +## Detailed Update History + +### Version 0.2.0 - Advanced Editing Capabilities +#### Hexadecimal Editing Enhancements +- Implemented sophisticated in-place hexadecimal editing +- Developed smart cursor positioning algorithm + * Maintains cursor location during edits + * Handles edits at any file location +- Preserved line formatting during byte modifications +- Added real-time hex value validation + +#### File Modification Safety +- Introduced file size change warning mechanism +- Implemented confirmation dialog for file size alterations +- Ensured user awareness of potential file structure changes + +#### Checksum Refinements +- Enhanced checksum calculation methods +- Supported variable-length byte sequences +- Implemented three distinct checksum types: + * 1-byte Sum Checksum + * 1-byte XOR Checksum + * 16-bit Checksum +- Provided hexadecimal representation of checksums + +#### Technical Improvements +- Added comprehensive error logging +- Improved input validation +- Enhanced user interaction during file editing + +### Version 0.3.0 - Binary Graph Enhancements +#### Graph Display Improvements +- Implemented custom Tkinter-based graph visualization +- Added support for 8-bit, 16-bit, and 32-bit data display +- Enhanced scrolling mechanism for viewing large datasets +- Dynamic point display control (5-1500 points) + +#### Axis Customization +- X-axis: + * Hexadecimal value display + * Special 16-bit mode with doubled values + * Automatic scaling based on visible points +- Y-axis: + * Fixed maximum values based on bit mode (255, 65535, 4294967295) + * Decimal value display + * Proper scaling for all data ranges + +#### Interactive Features +- Smooth scrolling with scrollbar and mouse wheel +- Dynamic graph updates when changing bit modes +- Automatic rescaling when changing number of visible points +- Default 512-point display for optimal performance + +#### Technical Improvements +- Implemented efficient canvas-based plotting +- Enhanced error handling and logging +- Improved memory management for large datasets +- Better user experience with responsive controls + +### Version 0.3.1 - Interactive Graph Hover +#### Enhanced Graph Interaction +- Implemented interactive hover functionality in binary graph +- Added dynamic vertical line tracking mouse position +- Hover label displays: + * X-axis position in hexadecimal + * Y-axis value in decimal and hexadecimal +- Seamless integration with existing 8-bit, 16-bit, and 32-bit modes +- Improved data visualization with real-time coordinate tracking + +### Upcoming Features +- Large file performance optimization +- Advanced search functionality +- More configurable display options +- Comprehensive unit testing + +## Logging +- Log file: hexawork.log +- Records operations and errors for debugging +- Format: timestamp, module, level, message + +## Contributing +Feel free to: +- Report bugs +- Suggest improvements +- Submit pull requests + +## License +This project is under the MIT license. diff --git a/list_EDC15.txt b/list_EDC15.txt new file mode 100644 index 0000000..49d54a0 --- /dev/null +++ b/list_EDC15.txt @@ -0,0 +1,17 @@ +EDC15C0 +EDC15C2 +EDC15C3 +EDC15C4 +EDC15C5 +EDC15C6 +EDC15C7 +EDC15C9 +EDC15C11 +EDC15C13 +EDC15M1 +EDC15P+ +EDC15P +EDC15P6 +EDC15V +EDC15V+ +EDC15VM diff --git a/liste type.csv b/liste type.csv new file mode 100644 index 0000000..320165a --- /dev/null +++ b/liste type.csv @@ -0,0 +1,11 @@ +ECU Type +EDC15C13 +EDC15C2 +EDC15C3 +EDC15C4 +EDC15C5 +EDC15C6 +EDC15C7 +EDC15C9 +EDC15P +EDC15V diff --git a/logger.py b/logger.py new file mode 100644 index 0000000..fa82f7e --- /dev/null +++ b/logger.py @@ -0,0 +1,44 @@ +import logging +import os +from datetime import datetime + +def setup_logger(name='hexaw_logger', log_dir='logs'): + """ + Set up a logger with file and console output. + + :param name: Name of the logger + :param log_dir: Directory to store log files + :return: Configured logger + """ + # Create logs directory if it doesn't exist + os.makedirs(log_dir, exist_ok=True) + + # Generate a log filename with timestamp + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + log_file = os.path.join(log_dir, f'{name}_{timestamp}.log') + + # Create logger + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) + + # Create file handler + file_handler = logging.FileHandler(log_file, encoding='utf-8') + file_handler.setLevel(logging.DEBUG) + + # Create console handler + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.INFO) + + # Create formatter + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_handler.setFormatter(formatter) + console_handler.setFormatter(formatter) + + # Add handlers to logger + logger.addHandler(file_handler) + logger.addHandler(console_handler) + + return logger + +# Create a global logger instance +logger = setup_logger() diff --git a/main.py b/main.py new file mode 100644 index 0000000..da3e1ca --- /dev/null +++ b/main.py @@ -0,0 +1,1179 @@ +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()