#!/usr/bin/env python3
"""
Psytec Server Connector
Un proxy temporaneo per connettere Minecraft Bedrock a server dedicati
"""

import tkinter as tk
from tkinter import ttk
import threading
import socket
import time
import struct
from datetime import datetime

class PsytecServerConnector:
    def __init__(self):
        # Configurazione server target
        self.target_server = "145.223.85.106"  # Server Psytec
        self.target_port = 19132
        
        # Configurazione proxy locale
        self.proxy_host = "127.0.0.1"
        self.proxy_port = 19132
        
        # Configurazione LAN broadcast
        self.lan_broadcast_port = 19132
        self.server_name = "PSYTEC SERVER"
        
        # Stato dell'applicazione
        self.proxy_running = False
        self.lan_broadcasting = False
        
        # Thread references
        self.proxy_thread = None
        self.lan_thread = None
        
        # Setup GUI
        self.setup_gui()
        
    def setup_gui(self):
        """Crea l'interfaccia grafica"""
        self.root = tk.Tk()
        self.root.title("Psytec Server Connector")
        self.root.geometry("450x400")
        self.root.resizable(True, True)
        self.root.minsize(400, 350)
        
        # Stile
        style = ttk.Style()
        style.theme_use('clam')
        
        # Configura stili personalizzati per i bottoni
        style.configure("Start.TButton", 
                       background="#4CAF50",  # Verde
                       foreground="white",
                       font=("Arial", 10, "bold"))
        style.map("Start.TButton",
                 background=[('active', '#45a049')])
        
        style.configure("Stop.TButton", 
                       background="#f44336",  # Rosso
                       foreground="white",
                       font=("Arial", 10, "bold"))
        style.map("Stop.TButton",
                 background=[('active', '#da190b')])
        
        # Frame principale
        main_frame = ttk.Frame(self.root, padding="20")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # Titolo
        title_label = ttk.Label(main_frame, text="Psytec Server Connector", 
                               font=("Arial", 16, "bold"))
        title_label.grid(row=0, column=0, columnspan=2, pady=(0, 20))
        
        # Informazioni server
        ttk.Label(main_frame, text="Server Target:").grid(row=1, column=0, sticky=tk.W, pady=5)
        self.server_label = ttk.Label(main_frame, text=f"{self.target_server}:{self.target_port}", 
                                     foreground="blue")
        self.server_label.grid(row=1, column=1, sticky=tk.W, padx=(10, 0), pady=5)
        
        ttk.Label(main_frame, text="Nome LAN:").grid(row=2, column=0, sticky=tk.W, pady=5)
        self.lan_name_label = ttk.Label(main_frame, text=self.server_name, 
                                       foreground="green")
        self.lan_name_label.grid(row=2, column=1, sticky=tk.W, padx=(10, 0), pady=5)
        
        # Stato
        ttk.Label(main_frame, text="Stato:").grid(row=3, column=0, sticky=tk.W, pady=5)
        self.status_label = ttk.Label(main_frame, text="⚫ Offline", foreground="red")
        self.status_label.grid(row=3, column=1, sticky=tk.W, padx=(10, 0), pady=5)
        
        # Proxy locale
        ttk.Label(main_frame, text="Proxy Locale:").grid(row=4, column=0, sticky=tk.W, pady=5)
        self.proxy_label = ttk.Label(main_frame, text=f"{self.proxy_host}:{self.proxy_port}")
        self.proxy_label.grid(row=4, column=1, sticky=tk.W, padx=(10, 0), pady=5)
        
        # Separatore
        separator = ttk.Separator(main_frame, orient='horizontal')
        separator.grid(row=5, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=20)
        
        # Bottoni
        self.start_button = ttk.Button(main_frame, text="Avvia Proxy", 
                                      command=self.toggle_server, style="Start.TButton")
        self.start_button.grid(row=6, column=0, columnspan=2, pady=10, sticky=(tk.W, tk.E))
        
        # Area log
        ttk.Label(main_frame, text="Log:").grid(row=7, column=0, sticky=(tk.W, tk.N), pady=(10, 0))
        
        log_frame = ttk.Frame(main_frame)
        log_frame.grid(row=8, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(5, 0))
        
        self.log_text = tk.Text(log_frame, height=8, wrap=tk.WORD, state=tk.DISABLED,
                               font=("Consolas", 9), bg="#f8f8f8", fg="#333")
        scrollbar = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log_text.yview)
        self.log_text.configure(yscrollcommand=scrollbar.set)
        
        self.log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        
        log_frame.grid_columnconfigure(0, weight=1)
        log_frame.grid_rowconfigure(0, weight=1)
        
        # Configurazione grid weights per il resize
        main_frame.grid_columnconfigure(1, weight=1)
        main_frame.grid_rowconfigure(8, weight=1)  # Il log può espandersi
        self.root.grid_columnconfigure(0, weight=1)
        self.root.grid_rowconfigure(0, weight=1)
        
        # Log iniziale
        self.log("Psytec Server Connector inizializzato")
        self.log(f"Server target: {self.target_server}:{self.target_port}")
        
    def log(self, message):
        """Aggiunge un messaggio al log"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        formatted_message = f"[{timestamp}] {message}\n"
        
        self.log_text.config(state=tk.NORMAL)
        self.log_text.insert(tk.END, formatted_message)
        self.log_text.see(tk.END)
        self.log_text.config(state=tk.DISABLED)
        
        # Auto-scroll
        self.root.update_idletasks()
    
    def toggle_server(self):
        """Avvia o ferma il proxy/LAN"""
        if not self.proxy_running:
            self.start_proxy()
        else:
            self.stop_proxy()
    
    def start_proxy(self):
        """Avvia il proxy e il LAN broadcasting"""
        try:
            self.log("Avvio proxy in corso...")
            
            # Avvia LAN broadcasting
            self.lan_broadcasting = True
            self.lan_thread = threading.Thread(target=self.lan_broadcaster, daemon=True)
            self.lan_thread.start()
            
            # Avvia proxy
            self.proxy_running = True
            self.proxy_thread = threading.Thread(target=self.proxy_server, daemon=True)
            self.proxy_thread.start()
            
            # Aggiorna GUI
            self.status_label.config(text="🟢 Online", foreground="green")
            self.start_button.config(text="Ferma Proxy", style="Stop.TButton")
            
            self.log(f"✅ LAN broadcasting attivo: '{self.server_name}'")
            self.log(f"✅ Proxy attivo su {self.proxy_host}:{self.proxy_port}")
            self.log("💡 Connettiti a 'PSYTEC SERVER' da Minecraft!")
            
        except Exception as e:
            self.log(f"❌ Errore nell'avvio: {e}")
            self.stop_proxy()
    
    def stop_proxy(self):
        """Ferma il proxy e il LAN broadcasting"""
        self.log("Arresto proxy in corso...")
        
        # Ferma broadcasting
        self.lan_broadcasting = False
        
        # Ferma proxy
        self.proxy_running = False
        
        # Aggiorna GUI
        self.status_label.config(text="⚫ Offline", foreground="red")
        self.start_button.config(text="Avvia Proxy", style="Start.TButton")
        
        self.log("🔴 Proxy fermato")
    
    def lan_broadcaster(self):
        """Thread per il broadcasting LAN di Minecraft Bedrock con listener bidirezionale"""
        broadcast_sock = None
        listen_sock = None
        
        try:
            # Socket per broadcasting (invio)
            broadcast_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            broadcast_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
            broadcast_sock.bind(('', 0))
            
            # Socket per listening (ricezione query Xbox) - PORTA 7551!
            listen_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            listen_sock.bind(('', 7551))  # Cambiato da 19132 a 7551!
            listen_sock.settimeout(0.5)
            
            # Ottieni IP locale per evitare loop
            local_ip = socket.gethostbyname(socket.gethostname())
            self.log(f"🖥️ IP locale: {local_ip}")
            
            self.log("🔄 Avvio LAN broadcaster bidirezionale...")
            self.log(f"📡 Listening su porta 7551 (Xbox discovery port)")
            
            # Carica il payload reale catturato da Wireshark
            real_payload_hex = """9c b6 d0 9c 12 f1 04 27 28 d4 01 fc 08 00 45 00
02 4c cb e9 00 00 80 11 87 10 c0 a8 b2 41 c0 a8
b2 14 1d 7f 1d 7f 02 38 7b 0b 83 a5 d7 46 c1 99
13 29 df 3a d5 33 4d f5 56 2e 43 02 05 d8 fe cb
34 95 f8 c8 c4 32 6e 62 10 88 1e 82 d8 86 15 ae
46 26 0f ac f5 cc a3 32 fd 6a b2 5f e1 be c6 f0
9f b7 fa 73 e7 98 f9 dc 72 bf d4 9d 66 00 d5 48
d9 59 9c f4 91 13 1c 9d cf bb 9e 8b 3a 6c e6 32
3c 83 e2 0a e9 76 a2 da 13 37 54 e3 e8 58 48 3f
1d c2 85 d1 d1 38 ac 6d 0b 03 3c 4b f4 56 91 91
19 93 a6 f4 bb 23 ee 3f 82 a8 58 32 18 30 f8 83
ed 31 f6 d7 94 9b 60 e1 f8 92 cf 63 77 1f 4b 06
92 98 e5 b8 2b f3 d5 12 68 9c 91 9c 0d 23 64 b4
bd 37 6c 0f 12 ea d4 33 53 52 9e 4b 36 5d 7b 00
58 df 95 35 b6 6b d4 ad 92 ee 63 2e ea f2 3a 58
c5 bd 51 67 21 86 67 30 df 36 43 67 66 26 d8 fd
61 21 a6 07 c5 ec 6c 98 05 83 c5 5d aa 16 2c 2e
92 c5 34 30 e0 23 9b 55 a1 f2 2d 37 fc 30 6d a2
ab 9a e4 b4 e1 33 31 5b 7c 9b f6 87 bd 82 8b 73
e4 a2 69 f0 20 f1 96 dc 80 3f 0d 35 20 24 86 6d
0b bc 77 39 ea 76 14 3f 0e 00 cb 30 50 1c 8a 06
ad 23 9b 09 e6 49 1c 48 4e c5 23 f3 90 5c 81 bc
a5 57 0a 46 c7 8c 9d 93 87 67 9b 30 68 de 37 2c
53 e7 f7 7a f6 51 c6 96 36 33 5c 54 06 0a d0 63
58 13 75 9d e4 53 d2 af 83 d6 ab 7b 5e f0 7c 51
69 ba 50 56 37 94 76 7f 03 d7 9a d8 f4 c7 16 79
7a 5d 42 1c a9 69 07 e1 aa 12 27 b9 c1 b7 75 ac
98 61 62 36 0f a9 2e d2 7f ac 5a 22 0e a9 05 61
17 da f0 99 32 4e db f1 1c bb 4a 40 2d ff 2e 4e
bb 81 cc 32 0f 7c 3b d6 f0 eb 5a 7d 9d fc 12 3e
c1 7d 65 53 74 35 c5 52 78 d7 9b de ef 6b 53 9c
58 5a 0b de 99 d7 79 6c 3f 28 0f 84 b6 12 3c 28
0d be 6b 37 3c 84 df 31 f0 4b d8 91 ef 22 b6 e2
a8 41 fc 47 45 33 07 9b 67 e5 9a 1e 32 a3 54 67
80 fa 20 04 6b 2c fe 38 70 5c fc 69 58 6f f1 dd
de 6f e4 ab 48 7c da 82 19 26 72 b7 7e 8d 03 fb
5c a9 95 89 20 8c 9e 1b 00 56 18 35 c7 47 48 b1
29 c7 1a 85 a0 05 a5 0e d4 3a"""
            
            # Converti hex string in bytes
            hex_clean = real_payload_hex.replace('\n', '').replace(' ', '')
            real_payload = bytes.fromhex(hex_clean)
            self.log(f"📦 Payload reale generato: {len(real_payload)} bytes")
            
            last_broadcast = 0
            
            while self.lan_broadcasting:
                try:
                    current_time = time.time()
                    
                    # BROADCAST ogni 2 secondi
                    if current_time - last_broadcast >= 2:
                        message = f"MCPE;{self.server_name};527;1.20.0;0;20;{int(current_time)};Survival;1;{self.proxy_port};{self.proxy_port};"
                        
                        try:
                            broadcast_sock.sendto(message.encode('utf-8'), ('255.255.255.255', 19132))
                        except Exception as e:
                            self.log(f"⚠️ Errore broadcast: {e}")
                        
                        last_broadcast = current_time
                        
                        if not hasattr(self, '_broadcast_logged'):
                            self.log(f"📤 Broadcasting: '{self.server_name}' ogni 2 secondi")
                            self._broadcast_logged = True
                    
                    # LISTEN per query da altri dispositivi
                    try:
                        data, addr = listen_sock.recvfrom(1024)
                        sender_ip = addr[0]
                        
                        # IGNORA i propri pacchetti!
                        if sender_ip == local_ip or sender_ip == '127.0.0.1':
                            continue
                        
                        # Xbox discovery sulla porta 7551
                        self.log(f"📥 Discovery query da {sender_ip} (len={len(data)})")
                        
                        # Rispondi con il payload reale catturato
                        if real_payload and len(real_payload) > 0:
                            # Usa il payload esatto da Wireshark
                            broadcast_sock.sendto(real_payload, (sender_ip, 7551))
                            self.log(f"📤 Risposta REALE inviata a {sender_ip} ({len(real_payload)} bytes)")
                        else:
                            self.log(f"❌ Payload reale non disponibile")
                            
                    except socket.timeout:
                        pass
                    except Exception as e:
                        if self.lan_broadcasting:
                            self.log(f"⚠️ Errore listener: {e}")
                    
                    time.sleep(0.1)
                    
                except Exception as e:
                    if self.lan_broadcasting:
                        self.log(f"⚠️ Errore nel loop LAN: {e}")
                        time.sleep(1)
                        
        except Exception as e:
            self.log(f"❌ Errore setup LAN broadcaster: {e}")
        finally:
            if broadcast_sock:
                broadcast_sock.close()
            if listen_sock:
                listen_sock.close()
            self.log("🔴 LAN broadcaster fermato")
    
    def proxy_server(self):
        """Thread per il server proxy"""
        try:
            proxy_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            proxy_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            proxy_socket.bind((self.proxy_host, self.proxy_port))
            proxy_socket.listen(5)
            proxy_socket.settimeout(1.0)  # Timeout per controllare il flag di stop
            
            self.log(f"🔧 Proxy in ascolto su {self.proxy_host}:{self.proxy_port}")
            
            while self.proxy_running:
                try:
                    client_socket, client_addr = proxy_socket.accept()
                    self.log(f"📥 Connessione da {client_addr}")
                    
                    # Gestisci connessione in thread separato
                    handler_thread = threading.Thread(
                        target=self.handle_connection, 
                        args=(client_socket,), 
                        daemon=True
                    )
                    handler_thread.start()
                    
                except socket.timeout:
                    continue  # Continua il loop per controllare proxy_running
                except Exception as e:
                    if self.proxy_running:
                        self.log(f"⚠️ Errore proxy: {e}")
                        
        except Exception as e:
            self.log(f"❌ Errore setup proxy: {e}")
        finally:
            if 'proxy_socket' in locals():
                proxy_socket.close()
    
    def handle_connection(self, client_socket):
        """Gestisce una singola connessione client"""
        try:
            # Connetti al server target
            target_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            target_socket.connect((self.target_server, self.target_port))
            
            self.log(f"🔗 Connessione stabilita con {self.target_server}:{self.target_port}")
            
            # Relay dei dati bidirezionale
            def relay_data(src, dst, direction):
                try:
                    while True:
                        data = src.recv(4096)
                        if not data:
                            break
                        dst.send(data)
                except:
                    pass
                finally:
                    src.close()
                    dst.close()
            
            # Thread per entrambe le direzioni
            t1 = threading.Thread(target=relay_data, args=(client_socket, target_socket, "C->S"), daemon=True)
            t2 = threading.Thread(target=relay_data, args=(target_socket, client_socket, "S->C"), daemon=True)
            
            t1.start()
            t2.start()
            
            # Aspetta che uno dei thread finisca
            t1.join()
            t2.join()
            
            self.log("🔌 Connessione chiusa")
            
        except Exception as e:
            self.log(f"❌ Errore connessione: {e}")
        finally:
            try:
                client_socket.close()
                if 'target_socket' in locals():
                    target_socket.close()
            except:
                pass
    
    def run(self):
        """Avvia l'applicazione"""
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.root.mainloop()
    
    def on_closing(self):
        """Gestisce la chiusura dell'applicazione"""
        if self.proxy_running:
            self.stop_proxy()
        self.root.destroy()

if __name__ == "__main__":
    app = PsytecServerConnector()
    app.run()
