import tkinter as tk from tkinter import filedialog, scrolledtext, ttk, messagebox import re import os from collections import Counter # Importações dos novos módulos de padrões from patterns.common_patterns import PATTERNS_COMMON, INITIAL_ANALYSIS_PATTERNS_KEYS from patterns.landrover_jaguar_patterns import PATTERNS_LANDROVER_JAGUAR from patterns.vag_patterns import PATTERNS_VAG from patterns.bmw_patterns import PATTERNS_BMW # Importar PATTERNS_BMW from Data.vag_ecu_group_handler import load_vag_ecu_list from Data.jlr_ecu_group_handler import load_jlr_ecu_list from Data.bmw_ecu_group_handler import load_bmw_ecu_list # Nova importação para BMW # No futuro, adicionar importações para outros módulos de marca aqui: # from patterns.bmw_patterns import PATTERNS_BMW # etc. # --- Constantes Globais --- BRAND_NAMES = ['VAG', 'LandRover/Jaguar', 'BMW', 'Mercedes', 'Renault/Dacia', 'Peugeot/Citroen', 'Opel', 'Fiat/Lancia/Alfa', 'Ford'] # Variáveis globais para armazenar referências a widgets e estado global_root_window = None global_file_path_var = None global_results_text_widget = None global_brand_buttons_frame = None global_category_buttons_frame = None global_ascii_text = None # Armazena o conteúdo ASCII do ficheiro lido global_progress_bar = None global_progress_label = None # Função para ler o ficheiro em modo binário e tentar decodificar para ASCII def read_file_content(file_path): try: with open(file_path, 'rb') as f: binary_content = f.read() # Tenta decodificar como ASCII, ignorando erros ou substituindo caracteres problemáticos ascii_content = binary_content.decode('ascii', errors='replace') return ascii_content except FileNotFoundError: print(f"Erro: Ficheiro não encontrado em {file_path}") return None except Exception as e: print(f"Erro ao ler ou decodificar o ficheiro {file_path}: {e}") return None # Função principal de análise de padrões ASCII a partir de uma string de conteúdo def analyze_ascii_patterns_from_file(file_content, current_patterns_to_search, progress_callback=None, target_pattern_name=None): results = {} search_dict = {} if target_pattern_name: if target_pattern_name in current_patterns_to_search: search_dict = {target_pattern_name: current_patterns_to_search[target_pattern_name]} else: print(f"Aviso: Padrão alvo '{target_pattern_name}' não encontrado no dicionário de pesquisa fornecido.") return {target_pattern_name: {}} else: search_dict = current_patterns_to_search for pattern_name, regex_pattern in search_dict.items(): try: flags = 0 # A flag re.IGNORECASE para 'Ecu Family' foi removida para restaurar o comportamento anterior. # Se a insensibilidade a maiúsculas/minúsculas for necessária para os KEYWORDS, # isso deve ser tratado diretamente no regex em common_patterns.py. if pattern_name == 'Possible PIN': pin_candidates_matches = re.finditer(r'\b([A-Z0-9]{4,8})\b', file_content) uppercase_block_counts = Counter() found_items_for_pin = {} for match_obj in pin_candidates_matches: block_match = match_obj.group(1) if block_match.isalnum() and block_match == block_match.upper(): uppercase_block_counts[block_match] += 1 for block, count in uppercase_block_counts.items(): if 2 <= count <= 3: found_items_for_pin[block] = count results[pattern_name] = found_items_for_pin else: matches = re.finditer(regex_pattern, file_content, flags) found_items = {} for match_obj in matches: item = match_obj.group(1) if match_obj.groups() else match_obj.group(0) if item: cleaned_item = ''.join(char for char in item if char.isprintable()).strip() if cleaned_item: found_items[cleaned_item] = found_items.get(cleaned_item, 0) + 1 results[pattern_name] = found_items except re.error as e: print(f"Erro de regex para o padrão '{pattern_name}' com regex '{regex_pattern}': {e}") results[pattern_name] = {"ERRO DE REGEX": 1} except Exception as e: print(f"Erro inesperado ao processar o padrão '{pattern_name}': {e}") results[pattern_name] = {"ERRO INESPERADO": 1} return results # Função para lidar com a seleção de ficheiro e realizar a análise inicial def handle_file_selection_and_initial_analysis(file_path_var): file_path = filedialog.askopenfilename( title="Selecionar ficheiro ECU", filetypes=(("Binary files", "*.bin"), ("All files", "*.*")) ) if not file_path: return file_path_var.set(os.path.basename(file_path)) global global_ascii_text global_ascii_text = read_file_content(file_path) if global_ascii_text is None: tk.messagebox.showerror("Erro de Leitura", f"Não foi possível ler ou decodificar o ficheiro: {file_path}", parent=global_root_window) clear_ui_state() return if global_results_text_widget: global_results_text_widget.config(state=tk.NORMAL) global_results_text_widget.delete(1.0, tk.END) global_results_text_widget.config(state=tk.DISABLED) for widget in global_brand_buttons_frame.winfo_children(): widget.destroy() for widget in global_category_buttons_frame.winfo_children(): widget.destroy() initial_patterns_dict = {key: PATTERNS_COMMON[key] for key in INITIAL_ANALYSIS_PATTERNS_KEYS if key in PATTERNS_COMMON} if not initial_patterns_dict: tk.messagebox.showwarning("Configuração", "Nenhum padrão de análise inicial definido ou encontrado em common_patterns.py.", parent=global_root_window) create_brand_buttons() return analysis_output = analyze_ascii_patterns_from_file(global_ascii_text, initial_patterns_dict) display_initial_results(analysis_output, global_results_text_widget, global_root_window) create_brand_buttons() global_progress_label.config(text="Análise inicial concluída. Selecione uma marca.") global_progress_bar['value'] = 100 global_root_window.update_idletasks() # Nova função para exibir os resultados iniciais combinados def display_initial_results(initial_results_map, results_text_widget, root_window): results_text_widget.config(state=tk.NORMAL) results_text_widget.delete(1.0, tk.END) results_text_widget.insert(tk.END, "--- Análise Inicial Rápida ---\n") found_any_overall = False for pattern_name in INITIAL_ANALYSIS_PATTERNS_KEYS: results_text_widget.insert(tk.END, f" --- {pattern_name} ---\n") matches_for_pattern = initial_results_map.get(pattern_name) if matches_for_pattern: found_any_overall = True for item, count in matches_for_pattern.items(): results_text_widget.insert(tk.END, f" {item}\n") # Removida a contagem de ocorrências else: results_text_widget.insert(tk.END, " Nenhum resultado encontrado para este padrão.\n") if not found_any_overall: results_text_widget.insert(tk.END, " Nenhum dos padrões de análise rápida encontrou correspondências no ficheiro.\n") results_text_widget.insert(tk.END, "\n--- Fim da Análise Inicial Rápida ---\n") # Adicionar nova secção para Grupos (VAG, JLR, etc.) results_text_widget.insert(tk.END, "\n---- Centralina Usada nestes Marcas----\n") vag_ecu_list = load_vag_ecu_list() jlr_ecu_list = load_jlr_ecu_list() bmw_ecu_list = load_bmw_ecu_list() found_in_vag_group = False found_in_jlr_group = False found_in_bmw_group = False any_group_found = False ecu_families_found = set() # Para armazenar as famílias de ECU encontradas na análise inicial if initial_results_map and 'Ecu Family' in initial_results_map: for ecu_family, count in initial_results_map['Ecu Family'].items(): ecu_families_found.add(ecu_family) if ecu_families_found: for ecu_family in ecu_families_found: if ecu_family in vag_ecu_list: found_in_vag_group = True if ecu_family in jlr_ecu_list: found_in_jlr_group = True if ecu_family in bmw_ecu_list: found_in_bmw_group = True if found_in_vag_group: results_text_widget.insert(tk.END, " - Grupo VAG\n") any_group_found = True if found_in_jlr_group: results_text_widget.insert(tk.END, " - Grupo Jaguar LandRover\n") any_group_found = True if found_in_bmw_group: results_text_widget.insert(tk.END, " - Grupo BMW\n") any_group_found = True if not any_group_found: results_text_widget.insert(tk.END, " - Nenhum grupo correspondente encontrado.\n") else: results_text_widget.insert(tk.END, " Nenhuma família de ECU encontrada na análise para verificar grupos.\n") results_text_widget.config(state=tk.DISABLED) if root_window: root_window.update_idletasks() # Função a ser chamada pelo clique de um botão de categoria (sub-análise) def run_single_analysis(pattern_name_key, ascii_content, results_widget, root, progress_bar, progress_label_widget, brand_context=None): if not ascii_content: tk.messagebox.showwarning("Aviso", "Conteúdo do ficheiro não está carregado.", parent=root) return progress_label_widget.config(text=f"A analisar para: {pattern_name_key}...") progress_bar['value'] = 0 root.update_idletasks() regex_to_use = None if brand_context == 'VAG' and pattern_name_key in PATTERNS_VAG: regex_to_use = PATTERNS_VAG[pattern_name_key] elif brand_context == 'LandRover/Jaguar' and pattern_name_key in PATTERNS_LANDROVER_JAGUAR: regex_to_use = PATTERNS_LANDROVER_JAGUAR[pattern_name_key] elif brand_context == 'BMW' and pattern_name_key in PATTERNS_BMW: regex_to_use = PATTERNS_BMW[pattern_name_key] # Adicionar elif para outras marcas aqui if regex_to_use is None and pattern_name_key in PATTERNS_COMMON: regex_to_use = PATTERNS_COMMON[pattern_name_key] if not regex_to_use: tk.messagebox.showerror("Erro de Configuração", f"Padrão '{pattern_name_key}' não encontrado para o contexto '{brand_context or 'Comum'}'.", parent=root) progress_label_widget.config(text=f"Erro: Padrão '{pattern_name_key}' não configurado.") return single_pattern_dict_to_search = {pattern_name_key: regex_to_use} analysis_result = analyze_ascii_patterns_from_file(ascii_content, single_pattern_dict_to_search, target_pattern_name=pattern_name_key) progress_bar['value'] = 50 root.update_idletasks() results_widget.config(state=tk.NORMAL) results_widget.delete(1.0, tk.END) matches_dict = analysis_result.get(pattern_name_key, {}) if matches_dict: results_widget.insert(tk.END, f"Resultados para '{pattern_name_key}':\n") for item, count in matches_dict.items(): results_widget.insert(tk.END, f" - {item} (Ocorrências: {count})\n") else: results_widget.insert(tk.END, f"Nenhum resultado encontrado para '{pattern_name_key}'.\n") results_widget.config(state=tk.DISABLED) progress_bar['value'] = 100 progress_label_widget.config(text=f"Análise para '{pattern_name_key}' concluída.") root.update_idletasks() def run_vag_sw_analysis(ascii_content, results_widget, root, progress_bar, progress_label_widget): if not ascii_content: messagebox.showwarning("Aviso", "Nenhum ficheiro carregado ou conteúdo ASCII não disponível.") return # Check if required patterns are defined missing_patterns = [] if 'PATTERNS_COMMON' not in globals() or not isinstance(PATTERNS_COMMON, dict) or \ 'Possible Software Number' not in PATTERNS_COMMON: missing_patterns.append("'Possible Software Number' de PATTERNS_COMMON") if 'PATTERNS_VAG' not in globals() or not isinstance(PATTERNS_VAG, dict) or \ 'VAG 10SW' not in PATTERNS_VAG: missing_patterns.append("'VAG 10SW' de PATTERNS_VAG") if missing_patterns: messagebox.showerror("Erro de Configuração", f"Padrões não encontrados ou mal configurados: {', '.join(missing_patterns)}.") return progress_label_widget.config(text="A analisar VAG SW...") progress_bar['value'] = 0 if root: root.update_idletasks() patterns_for_vag_sw = { 'Possible Software Number': PATTERNS_COMMON['Possible Software Number'], 'VAG 10SW': PATTERNS_VAG['VAG 10SW'] } all_results_map = {} total_patterns_to_analyze = len(patterns_for_vag_sw) for i, (name, pattern_regex) in enumerate(patterns_for_vag_sw.items()): progress_val = int(((i + 0.5) / total_patterns_to_analyze) * 100) progress_bar['value'] = progress_val progress_label_widget.config(text=f"A analisar VAG SW: {name} ({progress_val}%)") if root: root.update_idletasks() current_search_dict = {name: pattern_regex} # Pass progress_callback=None as analyze_ascii_patterns_from_file might not use it for single pattern analysis_results = analyze_ascii_patterns_from_file(ascii_content, current_search_dict, progress_callback=None, target_pattern_name=name) if name in analysis_results and analysis_results[name]: all_results_map[name] = analysis_results[name] results_widget.config(state=tk.NORMAL) results_widget.delete('1.0', tk.END) results_widget.insert(tk.END, "--- Análise VAG SW ---\n") displayed_sw_numbers = set() found_any_sw = False # Processar 'Possible Software Number' primeiro pattern_order = ['Possible Software Number', 'VAG 10SW'] for pattern_title in pattern_order: if pattern_title in all_results_map and all_results_map[pattern_title]: items_found = all_results_map[pattern_title] # Filtrar itens já exibidos, especialmente para 'VAG 10SW' unique_items_to_display = [] if items_found: sorted_items = sorted(items_found.items(), key=lambda x: x[1], reverse=True) for item, count in sorted_items: if item not in displayed_sw_numbers: unique_items_to_display.append(item) displayed_sw_numbers.add(item) if unique_items_to_display: found_any_sw = True results_widget.insert(tk.END, f" --- {pattern_title} ---\n") for item_text in unique_items_to_display: results_widget.insert(tk.END, f" {item_text}\n") elif pattern_title == 'Possible Software Number' and not found_any_sw: # Se PSN não achou nada, e é a primeira vez results_widget.insert(tk.END, f" --- {pattern_title} ---\n Nenhum resultado encontrado.\n") if not found_any_sw: results_widget.insert(tk.END, " Nenhum número de software VAG encontrado.\n") results_widget.insert(tk.END, "\n--- Fim da Análise VAG SW ---\n") results_widget.config(state=tk.DISABLED) progress_label_widget.config(text="Análise VAG SW concluída.") progress_bar['value'] = 100 if root: root.update_idletasks() # Função para limpar o estado da UI def clear_ui_state(): if global_file_path_var: global_file_path_var.set("Nenhum ficheiro selecionado") if global_results_text_widget: global_results_text_widget.config(state=tk.NORMAL) global_results_text_widget.delete(1.0, tk.END) global_results_text_widget.insert(tk.END, "Por favor, selecione um ficheiro para análise.") global_results_text_widget.config(state=tk.DISABLED) for frame in [global_brand_buttons_frame, global_category_buttons_frame]: if frame: for widget in frame.winfo_children(): widget.destroy() if global_progress_label: global_progress_label.config(text="Pronto para iniciar.") if global_progress_bar: global_progress_bar['value'] = 0 # Limpar o texto ASCII global para a próxima análise global global_ascii_text global_ascii_text = None if global_root_window: global_root_window.update_idletasks() # Função para criar os botões das marcas def create_brand_buttons(): for widget in global_brand_buttons_frame.winfo_children(): widget.destroy() for brand_name in BRAND_NAMES: button = ttk.Button(global_brand_buttons_frame, text=brand_name, command=lambda b_name=brand_name: show_brand_analysis_options(b_name)) button.pack(side=tk.LEFT, padx=2, pady=2, fill=tk.X, expand=True) if global_root_window: global_root_window.update_idletasks() # Função para mostrar opções de análise baseadas na marca def show_brand_analysis_options(brand_name): if not global_ascii_text: tk.messagebox.showwarning("Aviso", "Conteúdo do ficheiro não está carregado.", parent=global_root_window) return for widget in global_category_buttons_frame.winfo_children(): widget.destroy() patterns_to_show_for_brand_keys = [] for p_name in PATTERNS_COMMON.keys(): # Exclui 'Possible PIN' especificamente para a marca VAG ou BMW if (brand_name == 'VAG' and p_name == 'Possible PIN') or \ (brand_name == 'BMW' and p_name == 'Possible PIN'): continue if p_name not in INITIAL_ANALYSIS_PATTERNS_KEYS: patterns_to_show_for_brand_keys.append(p_name) brand_specific_patterns_for_this_brand_keys = [] if brand_name == 'VAG': brand_specific_patterns_for_this_brand_keys = list(PATTERNS_VAG.keys()) elif brand_name == 'LandRover/Jaguar': brand_specific_patterns_for_this_brand_keys = list(PATTERNS_LANDROVER_JAGUAR.keys()) elif brand_name == 'BMW': brand_specific_patterns_for_this_brand_keys = list(PATTERNS_BMW.keys()) # Adicionar elif para outras marcas aqui combined_keys = patterns_to_show_for_brand_keys + brand_specific_patterns_for_this_brand_keys patterns_to_show_for_brand_keys = sorted(list(set(combined_keys))) global_progress_label.config(text=f"Marca: {brand_name}. Selecione um tipo de análise.") global_progress_bar['value'] = 0 if not patterns_to_show_for_brand_keys: label_no_patterns = ttk.Label(global_category_buttons_frame, text=f"Nenhum padrão de análise definido para a marca {brand_name}.") label_no_patterns.pack(pady=10) global_root_window.update_idletasks() return for pattern_name_key in patterns_to_show_for_brand_keys: button_text = pattern_name_key current_command = None if brand_name == 'VAG': if pattern_name_key == 'Bosch SW': # Esta é a chave original que queremos substituir por 'VAG SW' button_text = 'VAG SW' current_command = lambda: run_vag_sw_analysis( # run_vag_sw_analysis agora só faz 10SW global_ascii_text, global_results_text_widget, global_root_window, global_progress_bar, global_progress_label ) elif pattern_name_key == 'VAG 10SW': # Esta chave vem de PATTERNS_VAG.keys(), não queremos um botão para ela. continue # Pula a criação de um botão dedicado 'VAG 10SW' elif pattern_name_key == 'VAG Specific PartNumbers': # Para este, queremos um botão que chame run_single_analysis como normal button_text = 'VAG Specific PartNumbers' current_command = lambda p_name=pattern_name_key: run_single_analysis( p_name, global_ascii_text, global_results_text_widget, global_root_window, global_progress_bar, global_progress_label, brand_context=brand_name ) else: # Para outros padrões comuns ou específicos de VAG que não são os acima current_command = lambda p_name=pattern_name_key: run_single_analysis( p_name, global_ascii_text, global_results_text_widget, global_root_window, global_progress_bar, global_progress_label, brand_context=brand_name ) else: # Para marcas que não são VAG current_command = lambda p_name=pattern_name_key: run_single_analysis( p_name, global_ascii_text, global_results_text_widget, global_root_window, global_progress_bar, global_progress_label, brand_context=brand_name ) if current_command: # Só cria o botão se um comando foi definido button = ttk.Button(global_category_buttons_frame, text=button_text, command=current_command) button.pack(side=tk.LEFT, padx=2, pady=2, fill=tk.X, expand=True) if global_root_window: global_root_window.update_idletasks() # Função principal para criar a janela e os widgets def create_main_window(): global global_root_window, global_file_path_var, global_results_text_widget global global_brand_buttons_frame, global_category_buttons_frame, global_progress_bar, global_progress_label root = tk.Tk() root.title("Analisador ASCII de Ficheiros ECU - Refatorado") global_root_window = root style = ttk.Style(root) try: style.theme_use('clam') # 'clam' é uma boa opção multiplataforma except tk.TclError: # Fallback para um tema padrão se 'clam' não estiver disponível # Isto pode acontecer em algumas instalações mínimas de Tk print("Tema 'clam' não encontrado, usando tema padrão do Tk.") pass # Usa o tema padrão do sistema file_path_var = tk.StringVar() global_file_path_var = file_path_var file_label = ttk.Label(root, textvariable=file_path_var) file_label.pack(pady=5, padx=10, fill=tk.X) select_button = ttk.Button(root, text="Selecionar Ficheiro e Iniciar Análise", command=lambda: handle_file_selection_and_initial_analysis(file_path_var)) select_button.pack(pady=5, padx=10, fill=tk.X) # pady ajustado para consistência progress_frame = ttk.Frame(root) progress_frame.pack(pady=5, padx=10, fill=tk.X) progress_label = ttk.Label(progress_frame, text="Pronto para iniciar.") progress_label.pack(side=tk.LEFT, padx=5) progress_bar = ttk.Progressbar(progress_frame, orient='horizontal', mode='determinate', length=200) progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True) global_progress_bar = progress_bar global_progress_label = progress_label brand_buttons_frame = ttk.Frame(root) brand_buttons_frame.pack(pady=5, padx=10, fill=tk.X) global_brand_buttons_frame = brand_buttons_frame category_buttons_frame = ttk.Frame(root) category_buttons_frame.pack(pady=5, padx=10, fill=tk.X) global_category_buttons_frame = category_buttons_frame results_text_widget = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=100, height=20, state=tk.DISABLED) results_text_widget.pack(pady=10, padx=10, fill=tk.BOTH, expand=True) global_results_text_widget = results_text_widget clear_ui_state() # Limpa a UI antes de iniciar root.mainloop() if __name__ == "__main__": create_main_window()