1180 lines
50 KiB
Python
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()
|