Files
Hex_Tuning_EDC15/main.py
2024-12-06 09:37:01 -08:00

1180 lines
50 KiB
Python

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, '<<Selection>>', on_select)
text_widget.bind('<ButtonRelease-1>', 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('<Key>', self.on_hex_text_key)
# Bind selection events
self.hex_text.bind('<<Selection>>', self.on_text_select)
self.hex_text.bind('<ButtonRelease-1>', self.on_text_select)
self.hex_text.bind('<KeyRelease>', 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()