Files
Hexa_Tuning_V2/main.py
2024-12-05 01:24:00 -08:00

1094 lines
46 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
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)
# 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="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 f1, open(modified_path, 'rb') as f2:
original_data = f1.read()
modified_data = f2.read()
# Check file sizes
if len(original_data) != len(modified_data):
raise ValueError(f"File sizes do not match!\nOriginal: {len(original_data)} bytes\nModified: {len(modified_data)} bytes")
# Create comparison window
compare_window = tk.Toplevel(self.master)
compare_window.title("File Comparison")
compare_window.geometry("1400x850")
# Create main frame with paned window
main_pane = ttk.PanedWindow(compare_window, orient=tk.VERTICAL)
main_pane.pack(fill=tk.BOTH, expand=True)
# Top pane for file comparison
top_pane = ttk.Frame(main_pane)
main_pane.add(top_pane)
# Navigation frame
nav_frame = ttk.Frame(top_pane)
nav_frame.pack(fill=tk.X, pady=5)
# Create horizontal paned window for files and diff
file_pane = ttk.PanedWindow(top_pane, orient=tk.HORIZONTAL)
file_pane.pack(fill=tk.BOTH, expand=True)
# Create frames for original and modified files with their order analysis
original_container = ttk.Frame(file_pane)
modified_container = ttk.Frame(file_pane)
diff_frame = ttk.Frame(file_pane)
file_pane.add(original_container, weight=1)
file_pane.add(modified_container, weight=1)
file_pane.add(diff_frame, weight=1)
# Original file section
original_file_frame = ttk.Frame(original_container)
original_file_frame.pack(fill=tk.BOTH, expand=True)
# Create labels for file names
ttk.Label(original_file_frame, text=f"Original: {os.path.basename(original_path)}").pack()
# Create scrolled text widgets for hex views
original_text = scrolledtext.ScrolledText(original_file_frame, wrap=tk.NONE, font=('Courier', 10))
original_text.pack(fill=tk.BOTH, expand=True)
# Modified file section
modified_file_frame = ttk.Frame(modified_container)
modified_file_frame.pack(fill=tk.BOTH, expand=True)
# Create labels for file names
ttk.Label(modified_file_frame, text=f"Modified: {os.path.basename(modified_path)}").pack()
modified_text = scrolledtext.ScrolledText(modified_file_frame, wrap=tk.NONE, font=('Courier', 10))
modified_text.pack(fill=tk.BOTH, expand=True)
# Order analysis frames for original and modified files
original_order_container = ttk.Frame(original_container)
original_order_container.pack(fill=tk.X)
modified_order_container = ttk.Frame(modified_container)
modified_order_container.pack(fill=tk.X)
# Create differences table
columns = ('Address', 'Original', 'Modified')
diff_table = ttk.Treeview(diff_frame, columns=columns, show='headings')
for col in columns:
diff_table.heading(col, text=col)
diff_table.column(col, width=100)
# Add scrollbar to table
table_scroll = ttk.Scrollbar(diff_frame, orient=tk.VERTICAL, command=diff_table.yview)
diff_table.configure(yscrollcommand=table_scroll.set)
# Pack table and scrollbar
table_scroll.pack(side=tk.RIGHT, fill=tk.Y)
diff_table.pack(fill=tk.BOTH, expand=True)
# Find and store differences
differences = list(self.find_differences(original_data, modified_data))
# Populate differences table
for offset, orig, mod in differences:
diff_table.insert('', tk.END, values=(
f"{offset:08X}",
orig,
mod
))
# Populate hex views
for i in range(0, len(original_data), 16):
# Format offset
offset = f"{i:08X}"
# Original file line
orig_line = [offset, ": "]
mod_line = [offset, ": "]
# Process 16 bytes per line
for j in range(16):
if i + j < len(original_data):
orig_byte = original_data[i + j]
mod_byte = modified_data[i + j]
# Check for differences
if orig_byte != mod_byte:
orig_line.append(("normal", f"{orig_byte:02X} "))
mod_line.append(("red", f"{mod_byte:02X} "))
else:
orig_line.append(("normal", f"{orig_byte:02X} "))
mod_line.append(("normal", f"{mod_byte:02X} "))
# Add ASCII representation
orig_line.append(" ")
mod_line.append(" ")
for j in range(16):
if i + j < len(original_data):
orig_byte = original_data[i + j]
mod_byte = modified_data[i + j]
# ASCII for original
if 32 <= orig_byte <= 126:
orig_line.append(("normal", chr(orig_byte)))
else:
orig_line.append(("normal", "."))
# ASCII for modified
if 32 <= mod_byte <= 126:
mod_line.append(("normal" if orig_byte == mod_byte else "red", chr(mod_byte)))
else:
mod_line.append(("normal" if orig_byte == mod_byte else "red", "."))
orig_line.append("\n")
mod_line.append("\n")
# Display lines
original_text.tag_config("red", foreground="black", background="lightcoral")
modified_text.tag_config("red", foreground="black", background="lightcoral")
# Display original line
for part in orig_line:
if isinstance(part, tuple):
color, text = part
original_text.insert(tk.END, text)
else:
original_text.insert(tk.END, part)
# Display modified line
for part in mod_line:
if isinstance(part, tuple):
color, text = part
if color == "red":
modified_text.insert(tk.END, text, "red")
else:
modified_text.insert(tk.END, text)
else:
modified_text.insert(tk.END, part)
# Initial order analysis for first 16 bytes
original_analysis = self.generate_order_analysis(original_data[:16])
modified_analysis = self.generate_order_analysis(modified_data[:16])
# Create initial order analysis frames
self.original_order_frame = self.create_order_analysis_frame(original_order_container, original_analysis)
self.modified_order_frame = self.create_order_analysis_frame(modified_order_container, modified_analysis)
# Make text views read-only
original_text.configure(state='disabled')
modified_text.configure(state='disabled')
# Navigation variables
current_diff_index = tk.IntVar(value=-1)
# Navigation buttons
def navigate_differences(direction):
nonlocal current_diff_index
if not differences:
messagebox.showinfo("Info", "No differences found.")
return
# Update index based on direction
if direction == 'next':
current_diff_index.set(min(current_diff_index.get() + 1, len(differences) - 1))
else: # previous
current_diff_index.set(max(current_diff_index.get() - 1, 0))
# Get current difference
offset, orig_byte, mod_byte = differences[current_diff_index.get()]
# Calculate line and scroll position
line_number = offset // 16
byte_in_line = offset % 16
# Enable text widgets temporarily to scroll
original_text.configure(state='normal')
modified_text.configure(state='normal')
# Scroll to the specific line
original_text.see(f"{line_number + 1}.0")
modified_text.see(f"{line_number + 1}.0")
# Remove previous highlights
original_text.tag_remove("current_diff", "1.0", tk.END)
modified_text.tag_remove("current_diff", "1.0", tk.END)
original_text.tag_remove("exact_diff", "1.0", tk.END)
modified_text.tag_remove("exact_diff", "1.0", tk.END)
# Highlight the entire line in yellow
original_text.tag_add("current_diff", f"{line_number + 1}.0", f"{line_number + 2}.0")
modified_text.tag_add("current_diff", f"{line_number + 1}.0", f"{line_number + 2}.0")
# Configure tags
original_text.tag_config("current_diff", background="lightyellow")
modified_text.tag_config("current_diff", background="lightyellow")
original_text.tag_config("exact_diff", background="lightcoral")
modified_text.tag_config("exact_diff", background="lightcoral")
# Calculate exact byte position in the text
# Each hex value is 3 characters long (2 hex + 1 space)
# Offset starts after the line number and ": "
hex_start_offset = 10 + (byte_in_line * 3)
# Add exact difference highlight
original_text.tag_add("exact_diff",
f"{line_number + 1}.{hex_start_offset}",
f"{line_number + 1}.{hex_start_offset + 2}")
modified_text.tag_add("exact_diff",
f"{line_number + 1}.{hex_start_offset}",
f"{line_number + 1}.{hex_start_offset + 2}")
# Disable text widgets again
original_text.configure(state='disabled')
modified_text.configure(state='disabled')
# Update diff table selection
diff_table.selection_remove(diff_table.selection())
diff_table.selection_add(diff_table.get_children()[current_diff_index.get()])
diff_table.focus(diff_table.get_children()[current_diff_index.get()])
diff_table.see(diff_table.get_children()[current_diff_index.get()])
# Create navigation buttons
prev_button = ttk.Button(nav_frame, text="Previous Difference",
command=lambda: navigate_differences('previous'))
prev_button.pack(side=tk.LEFT, padx=5)
next_button = ttk.Button(nav_frame, text="Next Difference",
command=lambda: navigate_differences('next'))
next_button.pack(side=tk.LEFT, padx=5)
# Difference count label
diff_count_label = ttk.Label(nav_frame,
text=f"Total Differences: {len(differences)}")
diff_count_label.pack(side=tk.LEFT, padx=10)
# Enable text selection and analysis
def create_selection_handler(text_widget, data):
def on_select(event=None):
try:
# Get selected text
if not text_widget.tag_ranges(tk.SEL):
return
start = text_widget.index(tk.SEL_FIRST)
end = text_widget.index(tk.SEL_LAST)
selected_text = text_widget.get(start, end)
# Extract hex values
hex_values = []
for part in selected_text.split():
if len(part) == 2 and all(c in '0123456789ABCDEFabcdef' for c in part):
hex_values.append(part.upper())
if hex_values:
# Convert to bytes
hex_str = ''.join(hex_values)
selected_bytes = bytes.fromhex(hex_str)
# Generate and update order analysis
analysis = self.generate_order_analysis(selected_bytes)
# Update the corresponding order analysis frame
if text_widget == original_text:
# Destroy existing frame and create new one
for widget in original_order_container.winfo_children():
widget.destroy()
self.create_order_analysis_frame(original_order_container, analysis)
else:
# Destroy existing frame and create new one
for widget in modified_order_container.winfo_children():
widget.destroy()
self.create_order_analysis_frame(modified_order_container, analysis)
except Exception as e:
self.logger.error(f"Error in selection handler: {e}")
return on_select
# Bind selection handlers
original_text.bind('<<Selection>>', create_selection_handler(original_text, original_data))
modified_text.bind('<<Selection>>', create_selection_handler(modified_text, modified_data))
except Exception as e:
self.logger.error(f"Error comparing files: {str(e)}")
messagebox.showerror("Error", str(e))
def find_differences(self, original_data, modified_data):
"""Find unique byte differences between two files"""
differences = []
i = 0
while i < min(len(original_data), len(modified_data)):
# If bytes are different
if original_data[i] != modified_data[i]:
# Look ahead to find the extent of the difference
diff_length = 1
while (i + diff_length < len(original_data) and
i + diff_length < len(modified_data) and
original_data[i + diff_length] != modified_data[i + diff_length]):
diff_length += 1
# Only add if this is the start of a unique difference sequence
# Check if this difference is not a continuation of a previous one
if not differences or i > differences[-1][0] + len(differences[-1][2]):
# Convert to hex for easier comparison
orig_sequence = ' '.join(f'{original_data[i + j]:02X}' for j in range(min(6, diff_length)))
mod_sequence = ' '.join(f'{modified_data[i + j]:02X}' for j in range(min(6, diff_length)))
differences.append((i, orig_sequence, mod_sequence))
# Skip to the end of this difference
i += diff_length
else:
i += 1
return differences
def generate_order_analysis(self, data):
"""
Generate order analysis for a given byte sequence
Args:
data (bytes): Byte sequence to analyze
Returns:
dict: Dictionary with order analysis results
"""
try:
# Normal Order Analysis
normal_hex_str = ''.join(f'{b:02X}' for b in data)
normal_int = int(normal_hex_str, 16)
# Reverse Order Analysis
reverse_hex_str = ''.join(f'{b:02X}' for b in reversed(data))
reverse_int = int(reverse_hex_str, 16)
# Calculate number of bits
num_bits = len(data) * 8
return {
'normal': {
'hex': normal_hex_str,
'dec': str(normal_int),
'ihex': f'{~normal_int & ((1 << num_bits) - 1):X}',
'idec': str(~normal_int & ((1 << num_bits) - 1))
},
'reverse': {
'hex': reverse_hex_str,
'dec': str(reverse_int),
'ihex': f'{~reverse_int & ((1 << num_bits) - 1):X}',
'idec': str(~reverse_int & ((1 << num_bits) - 1))
}
}
except Exception as e:
self.logger.error(f"Error in order analysis: {e}")
return None
def create_order_analysis_frame(self, parent, analysis):
"""
Create a frame to display order analysis
Args:
parent (tk.Widget): Parent widget
analysis (dict): Order analysis results
Returns:
ttk.LabelFrame: Frame with order analysis
"""
order_frame = ttk.LabelFrame(parent, text="Order Analysis")
order_frame.pack(fill=tk.X, pady=5)
# Normal Order
normal_frame = ttk.Frame(order_frame)
normal_frame.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
ttk.Label(normal_frame, text="Normal Order", font=('Helvetica', 10, 'bold')).pack()
ttk.Label(normal_frame, text=f"HEX: {analysis['normal']['hex']}").pack(anchor='w')
ttk.Label(normal_frame, text=f"DEC: {analysis['normal']['dec']}").pack(anchor='w')
ttk.Label(normal_frame, text=f"!HEX: {analysis['normal']['ihex']}").pack(anchor='w')
ttk.Label(normal_frame, text=f"!DEC: {analysis['normal']['idec']}").pack(anchor='w')
# Reverse Order
reverse_frame = ttk.Frame(order_frame)
reverse_frame.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
ttk.Label(reverse_frame, text="Reverse Order", font=('Helvetica', 10, 'bold')).pack()
ttk.Label(reverse_frame, text=f"HEX: {analysis['reverse']['hex']}").pack(anchor='w')
ttk.Label(reverse_frame, text=f"DEC: {analysis['reverse']['dec']}").pack(anchor='w')
ttk.Label(reverse_frame, text=f"!HEX: {analysis['reverse']['ihex']}").pack(anchor='w')
ttk.Label(reverse_frame, text=f"!DEC: {analysis['reverse']['idec']}").pack(anchor='w')
return order_frame
def create_file_selection_section(self):
file_frame = ttk.LabelFrame(self.main_frame, text="File Selection")
file_frame.pack(fill=tk.X, pady=5)
self.file_path = tk.StringVar()
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)
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)
self.normal_hex = tk.StringVar()
self.normal_dec = tk.StringVar()
self.normal_ihex = tk.StringVar()
self.normal_idec = tk.StringVar()
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)
self.reverse_hex = tk.StringVar()
self.reverse_dec = tk.StringVar()
self.reverse_ihex = tk.StringVar()
self.reverse_idec = tk.StringVar()
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):
file_path = filedialog.askopenfilename()
if file_path:
self.file_path.set(file_path)
self.inspect_file(file_path)
def inspect_file(self, file_path):
"""Inspect and display contents of a binary file"""
try:
# Read file in binary mode
with open(file_path, 'rb') as f:
data = f.read()
# 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(checksums['sum'])
self.checksum_xor.set(checksums['xor'])
self.checksum_16.set(checksums['sum16'])
# Store original file path for potential saving
self.current_file_path = file_path
# Update window title
self.master.title(f"HexaWork - {os.path.basename(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(self.current_file_path)
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 (2 bytes)
bit16_checksum = sum(data) & 0xFFFF
return {
'sum': f'{sum_checksum:02X}',
'xor': f'{xor_checksum:02X}',
'sum16': f'{bit16_checksum:04X}'
}
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):
"""Handle text selection in hex view"""
try:
# Get selected text
if not self.hex_text.tag_ranges(tk.SEL):
return
start = self.hex_text.index(tk.SEL_FIRST)
end = self.hex_text.index(tk.SEL_LAST)
selected_text = self.hex_text.get(start, end)
# Extract hex values
hex_values = []
for part in selected_text.split():
if len(part) == 2 and all(c in '0123456789ABCDEFabcdef' for c in part):
hex_values.append(part.upper())
if hex_values:
# Convert to bytes
hex_str = ''.join(hex_values)
self.selected_bytes = bytes.fromhex(hex_str)
self.logger.info(f"Converted to bytes: {self.selected_bytes.hex().upper()}")
# Update order analysis
self.update_order_analysis()
# Update checksums
checksums = self.calculate_checksums(self.selected_bytes)
self.checksum_sum.set(checksums['sum'])
self.checksum_xor.set(checksums['xor'])
self.checksum_16.set(checksums['sum16'])
else:
self.logger.warning("No valid hex values found in selection")
self.clear_order_analysis()
# Clear checksum values
self.checksum_sum.set("")
self.checksum_xor.set("")
self.checksum_16.set("")
except Exception as e:
self.logger.error(f"Error processing selection: {str(e)}\nTraceback: {traceback.format_exc()}")
self.clear_order_analysis()
# Clear checksum values
self.checksum_sum.set("")
self.checksum_xor.set("")
self.checksum_16.set("")
def clear_order_analysis(self):
"""Clear all order analysis values"""
for var in [self.normal_hex, self.normal_dec, self.normal_ihex, self.normal_idec,
self.reverse_hex, self.reverse_dec, self.reverse_ihex, self.reverse_idec]:
var.set("")
def update_order_analysis(self):
if not self.selected_bytes:
return
try:
# Normal Order Analysis
normal_hex_str = ''.join(f'{b:02X}' for b in self.selected_bytes)
self.normal_hex.set(normal_hex_str)
# Convert to integer (normal order)
normal_int = int(normal_hex_str, 16)
self.normal_dec.set(str(normal_int))
# Calculate !HEX (bitwise NOT)
num_bits = len(self.selected_bytes) * 8
normal_complement = ~normal_int & ((1 << num_bits) - 1)
self.normal_ihex.set(f'{normal_complement:X}')
# Calculate !DEC (decimal complement)
self.normal_idec.set(str(normal_complement))
# Reverse Order Analysis
reverse_hex_str = ''.join(f'{b:02X}' for b in reversed(self.selected_bytes))
self.reverse_hex.set(reverse_hex_str)
# Convert to integer (reverse order)
reverse_int = int(reverse_hex_str, 16)
self.reverse_dec.set(str(reverse_int))
# Calculate !HEX for reverse
reverse_complement = ~reverse_int & ((1 << num_bits) - 1)
self.reverse_ihex.set(f'{reverse_complement:X}')
# Calculate !DEC for reverse
self.reverse_idec.set(str(reverse_complement))
except Exception as e:
self.logger.error(f"Error updating order analysis: {e}")
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 main():
root = tk.Tk()
app = HexaWorkApp(root)
root.mainloop()
if __name__ == "__main__":
main()