Advertisement
PaffcioStudio

Untitled

Apr 29th, 2025
187
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 115.61 KB | None | 0 0
  1. import subprocess
  2. import sys
  3. import importlib.metadata
  4. import tkinter as tk
  5. from tkinter import ttk, messagebox, scrolledtext, filedialog, Menu
  6. import requests
  7. import os
  8. import json
  9. import threading
  10. import re
  11. import hashlib
  12. import pyperclip
  13. import zipfile
  14. from datetime import datetime
  15. import platform
  16. import webbrowser
  17. from packaging import version as pkg_version
  18. import humanize
  19. from colorama import init, Fore, Style
  20. from PIL import Image, ImageTk
  21. import shutil
  22. from uuid import uuid4
  23. import time
  24.  
  25. # Inicjalizacja colorama
  26. init(autoreset=True)
  27.  
  28. # Instalacja zależności
  29. def install_requirements():
  30.     required_libraries = ['requests', 'pyperclip', 'packaging', 'humanize', 'colorama', 'pillow']
  31.     for library in required_libraries:
  32.         try:
  33.             importlib.metadata.version(library)
  34.             print(f"{Fore.GREEN}[OK] {library} już zainstalowane.")
  35.         except importlib.metadata.PackageNotFoundError:
  36.             try:
  37.                 subprocess.check_call([sys.executable, "-m", "pip", "install", library])
  38.                 print(f"{Fore.CYAN}[INFO] {library} zainstalowane.")
  39.             except subprocess.CalledProcessError:
  40.                 print(f"{Fore.RED}[ERROR] Nie udało się zainstalować {library}")
  41.  
  42. install_requirements()
  43.  
  44. # Ścieżki
  45. BASE_DIR = os.path.join(os.getcwd(), "minecraft")
  46. CONFIG_FILE = os.path.join(BASE_DIR, "config.json")
  47. ASSETS_DIR = os.path.join(BASE_DIR, "assets")
  48. LIBRARIES_DIR = os.path.join(BASE_DIR, "libraries")
  49. NATIVES_DIR = os.path.join(BASE_DIR, "natives")
  50. LOGS_DIR = os.path.join(BASE_DIR, "logs")
  51. JAVA_DIR = os.path.join(BASE_DIR, "java")
  52. ICONS_DIR = os.path.join(BASE_DIR, "icons")
  53. MODPACKS_DIR = os.path.join(BASE_DIR, "modpacks")
  54. CACHE_DIR = os.path.join(BASE_DIR, "cache")
  55.  
  56. # Kolory i style
  57. LIGHT_THEME = {
  58.     "BG_COLOR": "#f0f0f0",
  59.     "FG_COLOR": "#333333",
  60.     "ACCENT_COLOR": "#2a9fd6",
  61.     "CONSOLE_BG": "#ffffff",
  62.     "CONSOLE_FG": "#006400",
  63.     "BUTTON_BG": "#e0e0e0",
  64.     "BUTTON_HOVER": "#d0d0d0",
  65.     "SIDEBAR_BG": "#e8e8e8",
  66.     "ACTIVE_TAB_COLOR": "#1e6f9f",
  67.     "HOVER_TAB_COLOR": "#d8d8d8",
  68.     "ENTRY_BG": "#ffffff",
  69.     "TREEVIEW_BG": "#ffffff",
  70.     "TREEVIEW_FG": "#333333",
  71.     "TREEVIEW_SELECTED": "#2a9fd6"
  72. }
  73.  
  74. DARK_THEME = {
  75.     "BG_COLOR": "#1a1a1a",
  76.     "FG_COLOR": "#ffffff",
  77.     "ACCENT_COLOR": "#2a9fd6",
  78.     "CONSOLE_BG": "#0d0d0d",
  79.     "CONSOLE_FG": "#00ff00",
  80.     "BUTTON_BG": "#333333",
  81.     "BUTTON_HOVER": "#555555",
  82.     "SIDEBAR_BG": "#222222",
  83.     "ACTIVE_TAB_COLOR": "#1e6f9f",
  84.     "HOVER_TAB_COLOR": "#3a3a3a",
  85.     "ENTRY_BG": "#333333",
  86.     "TREEVIEW_BG": "#333333",
  87.     "TREEVIEW_FG": "#ffffff",
  88.     "TREEVIEW_SELECTED": "#2a9fd6"
  89. }
  90.  
  91. # Globalne zmienne
  92. pending_instance_settings = {}
  93. pending_version = ""
  94. download_thread = None
  95. download_active = False
  96. global_progress_bar = None
  97. global_status_label = None
  98. instances = {}  # Globalna zmienna dla instancji
  99. java_versions_cache = {}  # Globalna zmienna dla wersji Javy
  100. console = None  # Globalna zmienna dla konsoli
  101. current_theme = "dark"  # 'dark' lub 'light'
  102. theme_colors = DARK_THEME
  103. mod_cache = {}  # Cache dla modów z CurseForge
  104. modpack_cache = {}  # Cache dla modpacków
  105. mod_loader_versions = {"fabric": {}, "forge": {}, "neoforge": {}}  # Cache wersji mod loaderów
  106.  
  107. # Funkcje narzędziowe
  108. def log_to_console(console, message, level="INFO"):
  109.     timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  110.     try:
  111.         console.config(state="normal")
  112.         if level == "ERROR":
  113.             console.tag_config("error", foreground="red")
  114.             console.insert(tk.END, f"[{timestamp}] {level}: {message}\n", "error")
  115.         elif level == "WARNING":
  116.             console.tag_config("warning", foreground="yellow")
  117.             console.insert(tk.END, f"[{timestamp}] {level}: {message}\n", "warning")
  118.         elif level == "SUCCESS":
  119.             console.tag_config("success", foreground="green")
  120.             console.insert(tk.END, f"[{timestamp}] {level}: {message}\n", "success")
  121.         else:
  122.             console.insert(tk.END, f"[{timestamp}] {level}: {message}\n")
  123.         console.see(tk.END)
  124.         console.config(state="disabled")
  125.     except:
  126.         os.makedirs(LOGS_DIR, exist_ok=True)
  127.         with open(os.path.join(LOGS_DIR, "error.log"), "a", encoding="utf-8") as f:
  128.             f.write(f"[{timestamp}] {level}: {message}\n")
  129.  
  130. def verify_sha1(file_path, expected_sha1):
  131.     sha1 = hashlib.sha1()
  132.     with open(file_path, "rb") as f:
  133.         for chunk in iter(lambda: f.read(4096), b""):
  134.             sha1.update(chunk)
  135.     return sha1.hexdigest() == expected_sha1
  136.  
  137. def save_config():
  138.     config = {
  139.         "default_settings": {
  140.             "username": username_var.get(),
  141.             "memory": memory_var.get(),
  142.             "shared_assets": shared_assets_var.get(),
  143.             "shared_libraries": shared_libraries_var.get(),
  144.             "shared_natives": shared_natives_var.get(),
  145.             "theme": current_theme
  146.         },
  147.         "version_filters": {
  148.             "snapshots": snapshots_var.get(),
  149.             "releases": releases_var.get(),
  150.             "alpha": alpha_var.get(),
  151.             "beta": beta_var.get()
  152.         },
  153.         "instances": instances,
  154.         "java_versions": java_versions_cache,
  155.         "mod_cache": mod_cache,
  156.         "modpack_cache": modpack_cache,
  157.         "mod_loader_versions": mod_loader_versions
  158.     }
  159.     os.makedirs(BASE_DIR, exist_ok=True)
  160.     with open(CONFIG_FILE, "w", encoding="utf-8") as f:
  161.         json.dump(config, f, indent=4)
  162.  
  163. def load_config():
  164.     global instances, java_versions_cache, username_var, memory_var, shared_assets_var, shared_libraries_var, shared_natives_var
  165.     global snapshots_var, releases_var, alpha_var, beta_var, current_theme, theme_colors, mod_cache, modpack_cache, mod_loader_versions
  166.    
  167.     if os.path.exists(CONFIG_FILE):
  168.         try:
  169.             with open(CONFIG_FILE, "r", encoding="utf-8") as f:
  170.                 config = json.load(f)
  171.                
  172.                 # Aktualizuj globalne zmienne zamiast tworzyć nowe
  173.                 if "default_settings" in config:
  174.                     default_settings = config["default_settings"]
  175.                     username_var.set(default_settings.get("username", "Player"))
  176.                     memory_var.set(default_settings.get("memory", "2"))
  177.                     shared_assets_var.set(default_settings.get("shared_assets", True))
  178.                     shared_libraries_var.set(default_settings.get("shared_libraries", True))
  179.                     shared_natives_var.set(default_settings.get("shared_natives", True))
  180.                     current_theme = default_settings.get("theme", "dark")
  181.                     theme_colors = DARK_THEME if current_theme == "dark" else LIGHT_THEME
  182.                
  183.                 if "version_filters" in config:
  184.                     version_filters = config["version_filters"]
  185.                     snapshots_var.set(version_filters.get("snapshots", True))
  186.                     releases_var.set(version_filters.get("releases", True))
  187.                     alpha_var.set(version_filters.get("alpha", False))
  188.                     beta_var.set(version_filters.get("beta", False))
  189.                
  190.                 if "instances" in config:
  191.                     instances = config["instances"]
  192.                
  193.                 if "java_versions" in config:
  194.                     java_versions_cache = config["java_versions"]
  195.                
  196.                 if "mod_cache" in config:
  197.                     mod_cache = config["mod_cache"]
  198.                
  199.                 if "modpack_cache" in config:
  200.                     modpack_cache = config["modpack_cache"]
  201.                
  202.                 if "mod_loader_versions" in config:
  203.                     mod_loader_versions = config["mod_loader_versions"]
  204.                
  205.                 return instances, java_versions_cache
  206.         except Exception as e:
  207.             log_to_console(console, f"Błąd wczytywania konfiguracji: {e}", "ERROR")
  208.             # Przywróć domyślne wartości w przypadku błędu
  209.             instances = {}
  210.             java_versions_cache = {}
  211.             return instances, java_versions_cache
  212.    
  213.     # Domyślne wartości jeśli plik nie istnieje
  214.     instances = {}
  215.     java_versions_cache = {}
  216.     return instances, java_versions_cache
  217.  
  218. def apply_theme():
  219.     global theme_colors
  220.     theme_colors = DARK_THEME if current_theme == "dark" else LIGHT_THEME
  221.    
  222.     style = ttk.Style()
  223.     style.theme_use('clam')
  224.     style.configure(".", background=theme_colors["BG_COLOR"], foreground=theme_colors["FG_COLOR"])
  225.     style.configure("TFrame", background=theme_colors["BG_COLOR"])
  226.     style.configure("TLabel", background=theme_colors["BG_COLOR"], foreground=theme_colors["FG_COLOR"], font=("Segoe UI", 10))
  227.     style.configure("TButton", background=theme_colors["BUTTON_BG"], foreground=theme_colors["FG_COLOR"], font=("Segoe UI", 10), borderwidth=0)
  228.     style.map("TButton",
  229.               background=[('active', theme_colors["BUTTON_HOVER"]), ('pressed', theme_colors["BUTTON_HOVER"])],
  230.               foreground=[('active', theme_colors["FG_COLOR"]), ('pressed', theme_colors["FG_COLOR"])])
  231.     style.configure("TEntry", fieldbackground=theme_colors["ENTRY_BG"], foreground=theme_colors["FG_COLOR"], insertcolor=theme_colors["FG_COLOR"], borderwidth=1)
  232.     style.configure("TCombobox", fieldbackground=theme_colors["ENTRY_BG"], foreground=theme_colors["FG_COLOR"], selectbackground=theme_colors["ACCENT_COLOR"])
  233.     style.configure("Horizontal.TProgressbar", troughcolor="#333333", background=theme_colors["ACCENT_COLOR"], thickness=10)
  234.     style.configure("Treeview", background=theme_colors["TREEVIEW_BG"], fieldbackground=theme_colors["TREEVIEW_BG"], foreground=theme_colors["TREEVIEW_FG"], rowheight=25)
  235.     style.map("Treeview", background=[('selected', theme_colors["TREEVIEW_SELECTED"])])
  236.     style.configure("Treeview.Heading", background=theme_colors["SIDEBAR_BG"], foreground=theme_colors["FG_COLOR"], font=("Segoe UI", 10, "bold"))
  237.    
  238.     # Aktualizuj kolory dla istniejących widgetów
  239.     for widget in root.winfo_children():
  240.         update_widget_colors(widget)
  241.  
  242. def update_widget_colors(widget):
  243.     if isinstance(widget, tk.Menu):
  244.         widget.config(bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"])
  245.     elif isinstance(widget, (tk.Frame, ttk.Frame)):
  246.         widget.config(bg=theme_colors["BG_COLOR"])
  247.     elif isinstance(widget, (tk.Label, ttk.Label)):
  248.         widget.config(bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"])
  249.     elif isinstance(widget, (tk.Button, ttk.Button)):
  250.         widget.config(bg=theme_colors["BUTTON_BG"], fg=theme_colors["FG_COLOR"],
  251.                       activebackground=theme_colors["BUTTON_HOVER"], activeforeground=theme_colors["FG_COLOR"])
  252.     elif isinstance(widget, tk.Entry):
  253.         widget.config(bg=theme_colors["ENTRY_BG"], fg=theme_colors["FG_COLOR"], insertbackground=theme_colors["FG_COLOR"])
  254.     elif isinstance(widget, tk.Text):
  255.         widget.config(bg=theme_colors["ENTRY_BG"], fg=theme_colors["FG_COLOR"])
  256.     elif isinstance(widget, tk.Listbox):
  257.         widget.config(bg=theme_colors["ENTRY_BG"], fg=theme_colors["FG_COLOR"])
  258.    
  259.     # Rekurencyjnie aktualizuj dzieci widgetu
  260.     for child in widget.winfo_children():
  261.         update_widget_colors(child)
  262.  
  263. def toggle_theme():
  264.     global current_theme
  265.     current_theme = "light" if current_theme == "dark" else "dark"
  266.     save_config()
  267.     apply_theme()
  268.  
  269. def get_versions():
  270.     try:
  271.         url = "https://launchermeta.mojang.com/mc/game/version_manifest.json"
  272.         resp = requests.get(url, timeout=10)
  273.         resp.raise_for_status()
  274.         manifest = resp.json()
  275.         versions = []
  276.         allowed_types = []
  277.         if snapshots_var.get():
  278.             allowed_types.append("snapshot")
  279.         if releases_var.get():
  280.             allowed_types.append("release")
  281.         if alpha_var.get():
  282.             allowed_types.append("old_alpha")
  283.         if beta_var.get():
  284.             allowed_types.append("old_beta")
  285.         for v in manifest["versions"]:
  286.             if v["type"] in allowed_types:
  287.                 versions.append(v["id"])
  288.         log_to_console(None, f"Pobrano {len(versions)} wersji (filtry: {allowed_types})", "INFO")
  289.         return sorted(versions, key=lambda x: x, reverse=True)
  290.     except Exception as e:
  291.         log_to_console(None, f"Nie udało się pobrać wersji: {e}", "ERROR")
  292.         return []
  293.  
  294. def get_version_info(version):
  295.     try:
  296.         url = "https://launchermeta.mojang.com/mc/game/version_manifest.json"
  297.         resp = requests.get(url, timeout=10)
  298.         resp.raise_for_status()
  299.         manifest = resp.json()
  300.         for v in manifest["versions"]:
  301.             if v["id"] == version:
  302.                 version_url = v["url"]
  303.                 return requests.get(version_url, timeout=10).json()
  304.         return None
  305.     except Exception as e:
  306.         log_to_console(None, f"Nie udało się pobrać info o wersji: {e}", "ERROR")
  307.         return None
  308.  
  309. def download_java(java_version, console):
  310.     try:
  311.         system = platform.system().lower()
  312.         arch = "x64"
  313.         base_url = "https://api.adoptium.net/v3/binary/latest/"
  314.         java_map = {
  315.             "1.8": "8",
  316.             "16": "16",
  317.             "17": "17",
  318.             "21": "21"
  319.         }
  320.         feature_version = java_map.get(java_version, java_version)
  321.         url = f"{base_url}{feature_version}/ga/{system}/{arch}/jdk/hotspot/normal/eclipse"
  322.         log_to_console(console, f"Pobieranie Javy {java_version} z {url}", "INFO")
  323.  
  324.         resp = requests.get(url, stream=True, timeout=10)
  325.         resp.raise_for_status()
  326.         total_size = int(resp.headers.get('content-length', 0))
  327.         java_dir = os.path.join(JAVA_DIR, f"jdk-{feature_version}")
  328.         os.makedirs(java_dir, exist_ok=True)
  329.         zip_path = os.path.join(java_dir, f"jdk-{feature_version}.zip")
  330.  
  331.         downloaded_size = 0
  332.         with open(zip_path, "wb") as f:
  333.             for chunk in resp.iter_content(chunk_size=8192):
  334.                 if chunk:
  335.                     f.write(chunk)
  336.                     downloaded_size += len(chunk)
  337.                     if total_size > 0 and global_progress_bar:
  338.                         update_progress(global_progress_bar, global_status_label,
  339.                                       (downloaded_size/total_size)*100, total_size-downloaded_size, "java", 1, 1)
  340.         log_to_console(console, f"Rozpakowywanie Javy do {java_dir}", "INFO")
  341.         with zipfile.ZipFile(zip_path, 'r') as zip_ref:
  342.             zip_ref.extractall(java_dir)
  343.         os.remove(zip_path)
  344.  
  345.         for root, _, files in os.walk(java_dir):
  346.             if "java.exe" in files:
  347.                 java_path = os.path.join(root, "java.exe")
  348.                 version = get_java_version(java_path)
  349.                 if version:
  350.                     log_to_console(console, f"Pobrano Javę: {java_path} (wersja: {version})", "SUCCESS")
  351.                     return java_path, version
  352.         log_to_console(console, "Nie znaleziono java.exe w pobranym archiwum!", "ERROR")
  353.         return None, None
  354.     except Exception as e:
  355.         log_to_console(console, f"Błąd pobierania Javy {java_version}: {e}", "ERROR")
  356.         return None, None
  357.  
  358. def find_java(required_version=None):
  359.     possible_paths = []
  360.     log_to_console(None, f"Szukam Javy {required_version or 'dowolnej'}...", "INFO")
  361.  
  362.     if os.path.exists(JAVA_DIR):
  363.         for java_folder in os.listdir(JAVA_DIR):
  364.             java_path = os.path.join(JAVA_DIR, java_folder, "bin", "java.exe")
  365.             if os.path.exists(java_path):
  366.                 version = get_java_version(java_path)
  367.                 if version and (not required_version or check_java_version(version, required_version)):
  368.                     possible_paths.append((java_path, version))
  369.                     log_to_console(None, f"Znaleziono Javę w {java_path} (wersja: {version})", "INFO")
  370.  
  371.     java_home = os.environ.get("JAVA_HOME")
  372.     if java_home:
  373.         java_path = os.path.join(java_home, "bin", "java.exe" if platform.system() == "Windows" else "java")
  374.         if os.path.exists(java_path):
  375.             version = get_java_version(java_path)
  376.             if version and (not required_version or check_java_version(version, required_version)):
  377.                 possible_paths.append((java_path, version))
  378.                 log_to_console(None, f"Znaleziono Javę w JAVA_HOME: {java_path} (wersja: {version})", "INFO")
  379.  
  380.     for base in [os.environ.get('ProgramFiles'), os.environ.get('ProgramFiles(x86)'), "C:\\Program Files\\Java", "C:\\Program Files (x86)\\Java"]:
  381.         if base:
  382.             java_dir = os.path.join(base, "Java")
  383.             if os.path.isdir(java_dir):
  384.                 for item in os.listdir(java_dir):
  385.                     java_path = os.path.join(java_dir, item, "bin", "java.exe")
  386.                     if os.path.exists(java_path):
  387.                         version = get_java_version(java_path)
  388.                         if version and (not required_version or check_java_version(version, required_version)):
  389.                             possible_paths.append((java_path, version))
  390.                             log_to_console(None, f"Znaleziono Javę w {java_path} (wersja: {version})", "INFO")
  391.  
  392.     try:
  393.         out = subprocess.check_output(["where", "java"], stderr=subprocess.DEVNULL).decode()
  394.         for line in out.splitlines():
  395.             line = line.strip()
  396.             if os.path.exists(line) and line not in [p[0] for p in possible_paths]:
  397.                 version = get_java_version(line)
  398.                 if version and (not required_version or check_java_version(version, required_version)):
  399.                     possible_paths.append((line, version))
  400.                     log_to_console(None, f"Znaleziono Javę w {line} (wersja: {version})", "INFO")
  401.     except:
  402.         pass
  403.  
  404.     if not possible_paths:
  405.         log_to_console(None, f"Nie znaleziono Javy {required_version}! Spróbuj pobrać automatycznie.", "ERROR")
  406.     return possible_paths
  407.  
  408. def get_java_version(java_path):
  409.     try:
  410.         result = subprocess.run([java_path, "-version"], stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
  411.         version_line = result.stderr.split('\n')[0]
  412.         version_match = re.search(r'version "([^"]+)"', version_line)
  413.         if version_match:
  414.             version = version_match.group(1)
  415.             is_64bit = "64-Bit" in result.stderr
  416.             log_to_console(None, f"Java {java_path}: {version}, 64-bit: {is_64bit}", "INFO")
  417.             return version if is_64bit else None
  418.     except Exception as e:
  419.         log_to_console(None, f"Błąd sprawdzania wersji Javy {java_path}: {e}", "ERROR")
  420.     return None
  421.  
  422. def check_java_version(installed_version, required_version):
  423.     try:
  424.         installed = installed_version.split("_")[0]
  425.         if required_version == "1.8":
  426.             return installed.startswith("1.8.")
  427.         if required_version == "16":
  428.             return installed.startswith("16.")
  429.         if required_version == "17":
  430.             return installed.startswith("17.")
  431.         if required_version == "21":
  432.             return installed.startswith("21.")
  433.         return pkg_version.parse(installed) >= pkg_version.parse(required_version)
  434.     except:
  435.         return False
  436.  
  437. def is_new_launcher(version):
  438.     try:
  439.         ver_match = re.match(r'1\.(\d+)', version)
  440.         if ver_match:
  441.             ver_float = float(ver_match.group(1))
  442.             return ver_float >= 6
  443.         return True
  444.     except:
  445.         return True
  446.  
  447. def get_required_java(version, version_info):
  448.     if version_info and "javaVersion" in version_info:
  449.         major_version = version_info["javaVersion"].get("majorVersion", 8)
  450.         return str(major_version)
  451.     try:
  452.         if re.match(r'(\d+w\d+[a-z])', version):  # Snapshoty
  453.             return "1.8"  # Większość starych snapshotów używa Javy 8
  454.         ver = pkg_version.parse(version)
  455.         if ver >= pkg_version.parse("1.21"):
  456.             return "21"
  457.         if ver >= pkg_version.parse("1.17"):
  458.             return "17"  # 1.18 też używa Javy 17
  459.         return "1.8"
  460.     except pkg_version.InvalidVersion:
  461.         return "1.8"  # Domyślnie dla niestandardowych wersji
  462.  
  463. def download_file(url, path, progress_callback, expected_sha1=None, console=None):
  464.     try:
  465.         resp = requests.get(url, stream=True, timeout=10)
  466.         resp.raise_for_status()
  467.         total_size = int(resp.headers.get('content-length', 0))
  468.         downloaded_size = 0
  469.         os.makedirs(os.path.dirname(path), exist_ok=True)
  470.         with open(path, "wb") as f:
  471.             for chunk in resp.iter_content(chunk_size=8192):
  472.                 if chunk:
  473.                     f.write(chunk)
  474.                     downloaded_size += len(chunk)
  475.                     if total_size > 0 and progress_callback:
  476.                         progress_callback(downloaded_size, total_size)
  477.         if expected_sha1 and not verify_sha1(path, expected_sha1):
  478.             log_to_console(console, f"Błąd SHA1 dla {path}", "ERROR")
  479.             return False
  480.         return True
  481.     except Exception as e:
  482.         log_to_console(console, f"Błąd pobierania {path}: {e}", "ERROR")
  483.         return False
  484.  
  485. def download_assets(version, version_info, version_dir, shared_assets, console):
  486.     try:
  487.         asset_index = version_info.get("assetIndex", {})
  488.         asset_url = asset_index.get("url")
  489.         asset_id = asset_index.get("id")
  490.         if not asset_url:
  491.             log_to_console(console, "Brak assetIndex dla tej wersji", "WARNING")
  492.             return True
  493.  
  494.         resp = requests.get(asset_url, timeout=10)
  495.         resp.raise_for_status()
  496.         assets_data = resp.json()
  497.  
  498.         assets_dir = ASSETS_DIR if shared_assets else os.path.join(version_dir, "assets")
  499.         objects_dir = os.path.join(assets_dir, "objects")
  500.         indexes_dir = os.path.join(assets_dir, "indexes")
  501.         os.makedirs(objects_dir, exist_ok=True)
  502.         os.makedirs(indexes_dir, exist_ok=True)
  503.  
  504.         index_file = os.path.join(indexes_dir, f"{asset_id}.json")
  505.         log_to_console(console, f"Zapisywanie indeksu assetów: {index_file}", "INFO")
  506.         with open(index_file, "w", encoding="utf-8") as f:
  507.             json.dump(assets_data, f)
  508.  
  509.         total_assets = len(assets_data["objects"])
  510.         total_size = sum(asset["size"] for asset in assets_data["objects"].values())
  511.         downloaded_size = 0
  512.  
  513.         for i, (asset_name, asset_info) in enumerate(assets_data["objects"].items()):
  514.             asset_hash = asset_info["hash"]
  515.             asset_size = asset_info["size"]
  516.             asset_subpath = f"{asset_hash[:2]}/{asset_hash}"
  517.             asset_path = os.path.join(objects_dir, asset_subpath)
  518.             asset_url = f"https://resources.download.minecraft.net/{asset_subpath}"
  519.             if not os.path.exists(asset_path):
  520.                 log_to_console(console, f"Pobieranie assetu: {asset_name}", "INFO")
  521.                 if not download_file(
  522.                     asset_url, asset_path,
  523.                     lambda d, t: update_progress(global_progress_bar, global_status_label, (d/t)*100, t-d, "assets", i+1, total_assets),
  524.                     expected_sha1=asset_hash, console=console
  525.                 ):
  526.                     return False
  527.             downloaded_size += asset_size
  528.             update_progress(global_progress_bar, global_status_label, (downloaded_size/total_size)*100,
  529.                            total_size-downloaded_size, "assets", i+1, total_assets)
  530.         return True
  531.     except Exception as e:
  532.         log_to_console(console, f"Błąd pobierania assetów: {e}", "ERROR")
  533.         return False
  534.  
  535. def download_natives(version, version_info, version_dir, shared_natives, console):
  536.     try:
  537.         libraries = version_info.get("libraries", [])
  538.         natives_dir = NATIVES_DIR if shared_natives else os.path.join(version_dir, "natives")
  539.         os.makedirs(natives_dir, exist_ok=True)
  540.  
  541.         system = platform.system().lower()
  542.         natives_classifier = "natives-windows" if system == "windows" else "natives-osx" if system == "darwin" else "natives-linux"
  543.  
  544.         total_size = 0
  545.         natives_to_download = []
  546.         for lib in libraries:
  547.             if "downloads" in lib and "classifiers" in lib["downloads"]:
  548.                 classifiers = lib["downloads"]["classifiers"]
  549.                 if natives_classifier in classifiers:
  550.                     artifact = classifiers[natives_classifier]
  551.                     natives_to_download.append(artifact)
  552.                     total_size += artifact.get("size", 0)
  553.  
  554.         log_to_console(console, f"Pobieranie {len(natives_to_download)} natywnych bibliotek", "INFO")
  555.         downloaded_size = 0
  556.         for i, artifact in enumerate(natives_to_download):
  557.             native_url = artifact["url"]
  558.             native_sha1 = artifact["sha1"]
  559.             native_size = artifact.get("size", 0)
  560.             native_path = os.path.join(version_dir, f"temp_native_{i}.jar")
  561.             log_to_console(console, f"Pobieranie natywnej biblioteki: {native_url}", "INFO")
  562.             if not download_file(
  563.                 native_url, native_path,
  564.                 lambda d, t: update_progress(global_progress_bar, global_status_label, (d/t)*100, t-d, "natives", i+1, len(natives_to_download)),
  565.                 expected_sha1=native_sha1, console=console
  566.             ):
  567.                 return False
  568.             with zipfile.ZipFile(native_path, 'r') as zip_ref:
  569.                 zip_ref.extractall(natives_dir)
  570.             os.remove(native_path)
  571.             downloaded_size += native_size
  572.             update_progress(global_progress_bar, global_status_label, (downloaded_size/total_size)*100,
  573.                            total_size-downloaded_size, "natives", i+1, len(natives_to_download))
  574.         return True
  575.     except Exception as e:
  576.         log_to_console(console, f"Błąd pobierania natywnych bibliotek: {e}", "ERROR")
  577.         return False
  578.  
  579. def verify_instance(version, console):
  580.     instance = instances.get(version)
  581.     if not instance:
  582.         log_to_console(console, f"Instancja {version} nie istnieje!", "ERROR")
  583.         messagebox.showerror("Błąd", f"Instancja {version} nie istnieje!")
  584.         return False
  585.  
  586.     version_dir = instance["path"]
  587.     settings = instance["settings"]
  588.     info = get_version_info(version)
  589.     if not info:
  590.         log_to_console(console, f"Nie udało się pobrać info o wersji {version}", "ERROR")
  591.         return False
  592.  
  593.     log_to_console(console, f"Weryfikacja instancji {version}...", "INFO")
  594.     all_valid = True
  595.  
  596.     # Sprawdź client.jar
  597.     client_path = os.path.join(version_dir, f"{version}.jar")
  598.     client_sha1 = info["downloads"]["client"]["sha1"]
  599.     if not os.path.exists(client_path):
  600.         log_to_console(console, f"Brak client.jar dla {version}", "ERROR")
  601.         all_valid = False
  602.     elif not verify_sha1(client_path, client_sha1):
  603.         log_to_console(console, f"Błąd SHA1 dla client.jar ({version})", "ERROR")
  604.         all_valid = False
  605.     else:
  606.         log_to_console(console, f"client.jar OK", "INFO")
  607.  
  608.     # Sprawdź biblioteki
  609.     libraries_dir = LIBRARIES_DIR if settings.get("shared_libraries", True) else os.path.join(version_dir, "libraries")
  610.     for lib in info.get("libraries", []):
  611.         if "downloads" in lib and "artifact" in lib["downloads"]:
  612.             artifact = lib["downloads"]["artifact"]
  613.             lib_path = artifact["path"].replace("/", os.sep)
  614.             full_lib_path = os.path.join(libraries_dir, lib_path)
  615.             lib_sha1 = artifact["sha1"]
  616.             if not os.path.exists(full_lib_path):
  617.                 log_to_console(console, f"Brak biblioteki {lib_path}", "ERROR")
  618.                 all_valid = False
  619.             elif not verify_sha1(full_lib_path, lib_sha1):
  620.                 log_to_console(console, f"Błąd SHA1 dla biblioteki {lib_path}", "ERROR")
  621.                 all_valid = False
  622.             else:
  623.                 log_to_console(console, f"Biblioteka {lib_path} OK", "INFO")
  624.  
  625.     # Sprawdź natywne biblioteki
  626.     natives_dir = NATIVES_DIR if settings.get("shared_natives", True) else os.path.join(version_dir, "natives")
  627.     system = platform.system().lower()
  628.     natives_classifier = "natives-windows" if system == "windows" else "natives-osx" if system == "darwin" else "natives-linux"
  629.     for lib in info.get("libraries", []):
  630.         if "downloads" in lib and "classifiers" in lib["downloads"]:
  631.             classifiers = lib["downloads"]["classifiers"]
  632.             if natives_classifier in classifiers:
  633.                 artifact = classifiers[natives_classifier]
  634.                 native_path = os.path.join(version_dir, f"temp_native_check.jar")
  635.                 if not download_file(artifact["url"], native_path, None, artifact["sha1"], console):
  636.                     log_to_console(console, f"Błąd weryfikacji natywnej biblioteki {artifact['url']}", "ERROR")
  637.                     all_valid = False
  638.                 else:
  639.                     log_to_console(console, f"Natywna biblioteka {artifact['url']} OK", "INFO")
  640.                 if os.path.exists(native_path):
  641.                     os.remove(native_path)
  642.  
  643.     # Sprawdź assets
  644.     assets_dir = ASSETS_DIR if settings.get("shared_assets", True) else os.path.join(version_dir, "assets")
  645.     asset_index = info.get("assetIndex", {})
  646.     if asset_index.get("url"):
  647.         resp = requests.get(asset_index["url"], timeout=10)
  648.         if resp.status_code == 200:
  649.             assets_data = resp.json()
  650.             for asset_name, asset_info in assets_data["objects"].items():
  651.                 asset_hash = asset_info["hash"]
  652.                 asset_subpath = f"{asset_hash[:2]}/{asset_hash}"
  653.                 asset_path = os.path.join(assets_dir, "objects", asset_subpath)
  654.                 if not os.path.exists(asset_path):
  655.                     log_to_console(console, f"Brak assetu {asset_name}", "ERROR")
  656.                     all_valid = False
  657.                 elif not verify_sha1(asset_path, asset_hash):
  658.                     log_to_console(console, f"Błąd SHA1 dla assetu {asset_name}", "ERROR")
  659.                     all_valid = False
  660.                 else:
  661.                     log_to_console(console, f"Asset {asset_name} OK", "INFO")
  662.         else:
  663.             log_to_console(console, f"Nie udało się pobrać indeksu assetów", "ERROR")
  664.             all_valid = False
  665.  
  666.     # Sprawdź i zregeneruj start.bat
  667.     bat_path = os.path.join(version_dir, "start.bat")
  668.     if not os.path.exists(bat_path):
  669.         log_to_console(console, f"Brak start.bat dla {version}, regeneruję...", "WARNING")
  670.         all_valid = False
  671.     else:
  672.         log_to_console(console, f"start.bat OK, regeneruję dla pewności...", "INFO")
  673.  
  674.     # Regeneracja start.bat
  675.     classpath = f"{version}.jar"
  676.     for lib in info.get("libraries", []):
  677.         if "downloads" in lib and "artifact" in lib["downloads"]:
  678.             lib_path = lib["downloads"]["artifact"]["path"].replace("/", os.sep)
  679.             classpath += f";{os.path.join(libraries_dir, lib_path)}"
  680.     assets_path = ASSETS_DIR if settings.get("shared_assets", True) else os.path.join(version_dir, "assets")
  681.     natives_path = NATIVES_DIR if settings.get("shared_natives", True) else os.path.join(version_dir, "natives")
  682.     java_path = instance.get("java_path", "")
  683.     asset_index = info.get("assetIndex", {}).get("id", version)
  684.     with open(bat_path, "w", encoding="utf-8") as f:
  685.         if is_new_launcher(version):
  686.             f.write(f'@echo off\n')
  687.             f.write(f'title Minecraft {version}\n')
  688.             f.write(f'"{java_path}" -Xmx{settings["memory"]}G -Djava.library.path="{natives_path}" -Dorg.lwjgl.util.Debug=true -cp "{classpath}" net.minecraft.client.main.Main ')
  689.             f.write(f'--username {settings["username"]} --version {version} --gameDir . ')
  690.             f.write(f'--assetsDir "{assets_path}" --assetIndex {asset_index} ')
  691.             f.write(f'--accessToken null --uuid 0 --userType legacy\n')
  692.             f.write(f'pause\n')
  693.         else:
  694.             f.write(f'@echo off\n')
  695.             f.write(f'title Minecraft {version}\n')
  696.             f.write(f'"{java_path}" -Xmx{settings["memory"]}G -cp "{version}.jar" net.minecraft.LauncherFrame {settings["username"]} player_session_id\n')
  697.             f.write(f'pause\n')
  698.     log_to_console(console, f"start.bat zregenerowany dla {version}", "SUCCESS")
  699.  
  700.     if all_valid:
  701.         log_to_console(console, f"Instancja {version} zweryfikowana poprawnie!", "SUCCESS")
  702.         messagebox.showinfo("Sukces", f"Instancja {version} jest kompletna!")
  703.     else:
  704.         log_to_console(console, f"Weryfikacja instancji {version} nieudana, ale start.bat zregenerowany!", "WARNING")
  705.         messagebox.showwarning("Uwaga", f"Instancja {version} miała problemy, ale start.bat został zregenerowany!")
  706.     return all_valid
  707.  
  708. def update_progress(progress_bar, status_label, progress, remaining, stage, current, total):
  709.     if progress_bar and status_label:
  710.         progress_bar["value"] = progress
  711.         remaining_str = humanize.naturalsize(remaining) if remaining else "0 B"
  712.         status_label.config(text=f"[{stage}] Postęp: {progress:.1f}% | Pozostało: {remaining_str} | {current}/{total}")
  713.         root.update()
  714.  
  715. def download_version(version, instance_settings, console):
  716.     global download_active
  717.     download_active = True
  718.     info = get_version_info(version)
  719.     if not info:
  720.         download_active = False
  721.         return
  722.  
  723.     java_major_version = get_required_java(version, info)
  724.     log_to_console(console, f"Wymagana Java: {java_major_version}", "INFO")
  725.  
  726.     java_paths = find_java(java_major_version)
  727.     if not java_paths:
  728.         if messagebox.askyesno("Brak Javy", f"Nie znaleziono Javy {java_major_version}. Pobrać automatycznie?"):
  729.             java_path, java_version = download_java(java_major_version, console)
  730.             if java_path:
  731.                 java_paths = [(java_path, java_version)]
  732.             else:
  733.                 log_to_console(console, f"Nie udało się pobrać Javy {java_major_version}!", "ERROR")
  734.                 messagebox.showerror("Błąd", f"Nie udało się pobrać Javy {java_major_version}!")
  735.                 download_active = False
  736.                 return
  737.         else:
  738.             log_to_console(console, f"Nie znaleziono Javy {java_major_version}! Wskaż ręcznie.", "ERROR")
  739.             messagebox.showerror("Błąd", f"Nie znaleziono Javy {java_major_version}! Pobierz z adoptium.net.")
  740.             download_active = False
  741.             return
  742.  
  743.     java_path, java_version = java_paths[0]
  744.     if instance_settings.get("java_path"):
  745.         java_path = instance_settings["java_path"]
  746.         java_version = get_java_version(java_path) or java_version
  747.     log_to_console(console, f"Używam Javy: {java_path} (wersja {java_version})", "INFO")
  748.  
  749.     version_dir = os.path.join(BASE_DIR, "instances", version)
  750.     os.makedirs(version_dir, exist_ok=True)
  751.     libraries_dir = LIBRARIES_DIR if instance_settings.get("shared_libraries", True) else os.path.join(version_dir, "libraries")
  752.     os.makedirs(libraries_dir, exist_ok=True)
  753.  
  754.     # Etap 1: Client.jar
  755.     update_progress(global_progress_bar, global_status_label, 0, 0, "client.jar", 1, 1)
  756.     global_status_label.config(text="[1/5] Pobieranie client.jar...")
  757.     client_url = info["downloads"]["client"]["url"]
  758.     client_sha1 = info["downloads"]["client"]["sha1"]
  759.     client_path = os.path.join(version_dir, f"{version}.jar")
  760.     log_to_console(console, f"Pobieranie client.jar dla {version}", "INFO")
  761.     if not download_file(
  762.         client_url, client_path,
  763.         lambda d, t: update_progress(global_progress_bar, global_status_label, (d/t)*100, t-d, "client.jar", 1, 1),
  764.         expected_sha1=client_sha1, console=console
  765.     ):
  766.         download_active = False
  767.         return
  768.  
  769.     # Etap 2: Biblioteki
  770.     update_progress(global_progress_bar, global_status_label, 0, 0, "biblioteki", 0, 0)
  771.     global_status_label.config(text="[2/5] Pobieranie bibliotek...")
  772.     libs = info.get("libraries", [])
  773.     total_libs_size = 0
  774.     libs_to_download = []
  775.     for lib in libs:
  776.         if "downloads" in lib and "artifact" in lib["downloads"]:
  777.             artifact = lib["downloads"]["artifact"]
  778.             lib_path = artifact["path"].replace("/", os.sep)
  779.             full_lib_path = os.path.join(libraries_dir, lib_path)
  780.             if not os.path.exists(full_lib_path):
  781.                 libs_to_download.append((lib, artifact))
  782.                 total_libs_size += artifact.get("size", 0)
  783.  
  784.     log_to_console(console, f"Pobieranie {len(libs_to_download)} bibliotek", "INFO")
  785.     downloaded_libs_size = 0
  786.     for i, (lib, artifact) in enumerate(libs_to_download):
  787.         lib_path = artifact["path"].replace("/", os.sep)
  788.         full_lib_path = os.path.join(libraries_dir, lib_path)
  789.         lib_url = artifact["url"]
  790.         lib_sha1 = artifact["sha1"]
  791.         lib_size = artifact.get("size", 0)
  792.         log_to_console(console, f"Pobieranie biblioteki: {lib_path}", "INFO")
  793.         if not download_file(
  794.             lib_url, full_lib_path,
  795.             lambda d, t: update_progress(global_progress_bar, global_status_label, (d/t)*100, t-d, "biblioteki", i+1, len(libs_to_download)),
  796.             expected_sha1=lib_sha1, console=console
  797.         ):
  798.             download_active = False
  799.         downloaded_libs_size += lib_size
  800.         update_progress(global_progress_bar, global_status_label, (downloaded_libs_size/total_libs_size)*100,
  801.                        total_libs_size-downloaded_libs_size, "biblioteki", i+1, len(libs_to_download))
  802.  
  803.     # Etap 3: Natives
  804.     update_progress(global_progress_bar, global_status_label, 0, 0, "natives", 0, 0)
  805.     global_status_label.config(text="[3/5] Pobieranie natywnych bibliotek...")
  806.     if not download_natives(version, info, version_dir, instance_settings.get("shared_natives", True), console):
  807.         download_active = False
  808.         return
  809.  
  810.     # Etap 4: Assets
  811.     update_progress(global_progress_bar, global_status_label, 0, 0, "assets", 0, 0)
  812.     global_status_label.config(text="[4/5] Pobieranie assetów...")
  813.     if not download_assets(version, info, version_dir, instance_settings.get("shared_assets", True), console):
  814.         download_active = False
  815.         return
  816.  
  817.     # Etap 5: Generowanie start.bat
  818.     update_progress(global_progress_bar, global_status_label, 0, 0, "start.bat", 1, 1)
  819.     global_status_label.config(text="[5/5] Generowanie start.bat...")
  820.     bat_path = os.path.join(version_dir, "start.bat")
  821.     classpath = f"{version}.jar"
  822.     for lib in libs:
  823.         if "downloads" in lib and "artifact" in lib["downloads"]:
  824.             lib_path = lib["downloads"]["artifact"]["path"].replace("/", os.sep)
  825.             classpath += f";{os.path.join(libraries_dir, lib_path)}"
  826.  
  827.     assets_path = ASSETS_DIR if instance_settings.get("shared_assets", True) else os.path.join(version_dir, "assets")
  828.     natives_path = NATIVES_DIR if instance_settings.get("shared_natives", True) else os.path.join(version_dir, "natives")
  829.     username = instance_settings.get("username", "Player").strip() or "Player"
  830.     memory = instance_settings.get("memory", "2")
  831.     asset_index = info.get("assetIndex", {}).get("id", version)
  832.  
  833.     with open(bat_path, "w", encoding="utf-8") as f:
  834.         if is_new_launcher(version):
  835.             f.write(f'@echo off\n')
  836.             f.write(f'title Minecraft {version}\n')
  837.             f.write(f'"{java_path}" -Xmx{memory}G -Djava.library.path="{natives_path}" -Dorg.lwjgl.util.Debug=true -cp "{classpath}" net.minecraft.client.main.Main ')
  838.             f.write(f'--username {username} --version {version} --gameDir . ')
  839.             f.write(f'--assetsDir "{assets_path}" --assetIndex {asset_index} ')
  840.             f.write(f'--accessToken null --uuid 0 --userType legacy\n')
  841.             f.write(f'pause\n')
  842.         else:
  843.             f.write(f'@echo off\n')
  844.             f.write(f'title Minecraft {version}\n')
  845.             f.write(f'"{java_path}" -Xmx{memory}G -cp "{version}.jar" net.minecraft.LauncherFrame {username} player_session_id\n')
  846.             f.write(f'pause\n')
  847.  
  848.     instances[version] = {
  849.         "path": version_dir,
  850.         "java_path": java_path,
  851.         "java_version": java_version,
  852.         "required_java": java_major_version,
  853.         "settings": instance_settings,
  854.         "ready": True,
  855.         "timestamp": datetime.now().isoformat(),
  856.         "mod_loader": None,
  857.         "mods": [],
  858.         "notes": "",
  859.         "play_time": 0
  860.     }
  861.     save_config()
  862.     update_progress(global_progress_bar, global_status_label, 100, 0, "gotowe", 1, 1)
  863.     global_status_label.config(text="Gotowe!")
  864.     log_to_console(console, f"Instancja {version} pobrana!", "SUCCESS")
  865.     messagebox.showinfo("Sukces", f"Instancja {version} gotowa!")
  866.     refresh_instances()
  867.     download_active = False
  868.  
  869. def run_game(version):
  870.     instance = instances.get(version)
  871.     if not instance or not instance.get("ready"):
  872.         messagebox.showerror("Błąd", f"Instancja {version} nie jest gotowa!")
  873.         return
  874.    
  875.     # Zaktualizuj czas grania
  876.     start_time = time.time()
  877.    
  878.     bat_path = os.path.join(instance["path"], "start.bat")
  879.     if os.path.exists(bat_path):
  880.         try:
  881.             subprocess.Popen(bat_path, cwd=instance["path"], shell=True)
  882.             log_to_console(console, f"Uruchomiono Minecraft {version}", "SUCCESS")
  883.            
  884.             # Po zamknięciu gry zaktualizuj czas grania
  885.             def update_play_time():
  886.                 end_time = time.time()
  887.                 play_time = end_time - start_time
  888.                 instances[version]["play_time"] += play_time
  889.                 save_config()
  890.            
  891.             # Uruchom wątek do monitorowania procesu gry
  892.             def monitor_process(process):
  893.                 process.wait()
  894.                 update_play_time()
  895.            
  896.             process = subprocess.Popen(bat_path, cwd=instance["path"], shell=True)
  897.             threading.Thread(target=monitor_process, args=(process,), daemon=True).start()
  898.            
  899.         except Exception as e:
  900.             log_to_console(console, f"Nie udało się uruchomić gry: {e}", "ERROR")
  901.             messagebox.showerror("Błąd", f"Nie udało się uruchomić gry: {e}")
  902.     else:
  903.         messagebox.showerror("Błąd", f"Brak start.bat dla {version}!")
  904.  
  905. def delete_instance(version):
  906.     if messagebox.askyesno("Potwierdź", f"Na pewno usunąć instancję {version}?"):
  907.         instance = instances.get(version)
  908.         if instance:
  909.             import shutil
  910.             try:
  911.                 shutil.rmtree(instance["path"])
  912.                 del instances[version]
  913.                 save_config()
  914.                 log_to_console(console, f"Instancja {version} usunięta", "SUCCESS")
  915.                 refresh_instances()
  916.             except Exception as e:
  917.                 log_to_console(console, f"Błąd usuwania instancji {version}: {e}", "ERROR")
  918.  
  919. def copy_console(console):
  920.     console.config(state="normal")
  921.     content = console.get("1.0", tk.END).strip()
  922.     pyperclip.copy(content)
  923.     console.config(state="disabled")
  924.     messagebox.showinfo("Sukces", "Konsola skopiowana!")
  925.  
  926. def clear_console(console):
  927.     console.config(state="normal")
  928.     console.delete("1.0", tk.END)
  929.     console.config(state="disabled")
  930.  
  931. def get_mod_loader_versions(loader, mc_version=None):
  932.     try:
  933.         if loader == "fabric":
  934.             if mc_version:
  935.                 url = f"https://meta.fabricmc.net/v2/versions/loader/{mc_version}"
  936.                 resp = requests.get(url, timeout=10)
  937.                 resp.raise_for_status()
  938.                 return resp.json()
  939.             else:
  940.                 url = "https://meta.fabricmc.net/v2/versions/loader"
  941.                 resp = requests.get(url, timeout=10)
  942.                 resp.raise_for_status()
  943.                 return resp.json()
  944.         elif loader == "forge":
  945.             url = "https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json"
  946.             resp = requests.get(url, timeout=10)
  947.             resp.raise_for_status()
  948.             data = resp.json()
  949.             versions = []
  950.             for mc_ver, builds in data["promos"].items():
  951.                 if "-" in mc_ver:  # Pomijamy wersje typu "1.16.5-latest"
  952.                     continue
  953.                 versions.append({
  954.                     "mc_version": mc_ver,
  955.                     "loader_version": builds.get("latest", ""),
  956.                     "recommended": builds.get("recommended", "")
  957.                 })
  958.             return versions
  959.         elif loader == "neoforge":
  960.             url = "https://maven.neoforged.net/api/maven/latest/version/releases/net%2Fneoforged%2Fneoforge"
  961.             resp = requests.get(url, timeout=10)
  962.             resp.raise_for_status()
  963.             return resp.json()
  964.         else:
  965.             return []
  966.     except Exception as e:
  967.         log_to_console(console, f"Błąd pobierania wersji {loader}: {e}", "ERROR")
  968.         return []
  969.  
  970. def install_mod_loader(version, loader, loader_version, console):
  971.     instance = instances.get(version)
  972.     if not instance:
  973.         log_to_console(console, f"Instancja {version} nie istnieje!", "ERROR")
  974.         return False
  975.    
  976.     version_dir = instance["path"]
  977.     java_path = instance.get("java_path", "")
  978.    
  979.     try:
  980.         if loader == "fabric":
  981.             # Pobierz installer Fabric
  982.             installer_url = f"https://meta.fabricmc.net/v2/versions/loader/{version}/{loader_version}/profile/json"
  983.             resp = requests.get(installer_url, timeout=10)
  984.             resp.raise_for_status()
  985.             profile = resp.json()
  986.            
  987.             # Zapisz profil jako fabric_profile.json
  988.             profile_path = os.path.join(version_dir, "fabric_profile.json")
  989.             with open(profile_path, "w", encoding="utf-8") as f:
  990.                 json.dump(profile, f)
  991.            
  992.             # Zaktualizuj start.bat
  993.             with open(os.path.join(version_dir, "start.bat"), "w", encoding="utf-8") as f:
  994.                 f.write(f'@echo off\n')
  995.                 f.write(f'title Minecraft {version} (Fabric)\n')
  996.                 f.write(f'"{java_path}" -Xmx{instance["settings"]["memory"]}G -Dfabric.gameJarPath={version}.jar -jar fabric-loader-{loader_version}.jar\n')
  997.                 f.write(f'pause\n')
  998.            
  999.             instances[version]["mod_loader"] = {"type": "fabric", "version": loader_version}
  1000.             save_config()
  1001.             return True
  1002.            
  1003.         elif loader == "forge" or loader == "neoforge":
  1004.             # Pobierz installer Forge/NeoForge
  1005.             installer_url = ""
  1006.             if loader == "forge":
  1007.                 installer_url = f"https://maven.minecraftforge.net/net/minecraftforge/forge/{version}-{loader_version}/forge-{version}-{loader_version}-installer.jar"
  1008.             else:
  1009.                 installer_url = f"https://maven.neoforged.net/releases/net/neoforged/forge/{version}-{loader_version}/forge-{version}-{loader_version}-installer.jar"
  1010.            
  1011.             installer_path = os.path.join(version_dir, f"{loader}_installer.jar")
  1012.             if not download_file(installer_url, installer_path, None, None, console):
  1013.                 return False
  1014.            
  1015.             # Uruchom installer
  1016.             log_to_console(console, f"Uruchamianie installera {loader}...", "INFO")
  1017.             process = subprocess.Popen([java_path, "-jar", installer_path, "--installServer"], cwd=version_dir)
  1018.             process.wait()
  1019.            
  1020.             # Zaktualizuj start.bat
  1021.             with open(os.path.join(version_dir, "start.bat"), "w", encoding="utf-8") as f:
  1022.                 f.write(f'@echo off\n')
  1023.                 f.write(f'title Minecraft {version} ({loader.capitalize()})\n')
  1024.                 f.write(f'"{java_path}" -Xmx{instance["settings"]["memory"]}G -jar {loader}-{version}-{loader_version}.jar\n')
  1025.                 f.write(f'pause\n')
  1026.            
  1027.             instances[version]["mod_loader"] = {"type": loader, "version": loader_version}
  1028.             save_config()
  1029.             return True
  1030.        
  1031.         return False
  1032.     except Exception as e:
  1033.         log_to_console(console, f"Błąd instalacji {loader}: {e}", "ERROR")
  1034.         return False
  1035.  
  1036. def search_mods(query, mc_version=None, loader=None):
  1037.     try:
  1038.         url = "https://api.curseforge.com/v1/mods/search"
  1039.         params = {
  1040.             "gameId": 432,  # Minecraft
  1041.             "searchFilter": query,
  1042.             "pageSize": 50
  1043.         }
  1044.         if mc_version:
  1045.             params["gameVersion"] = mc_version
  1046.         if loader:
  1047.             params["modLoaderType"] = 1 if loader == "forge" else 4 if loader == "fabric" else 0
  1048.        
  1049.         headers = {
  1050.             "x-api-key": "$2a$10$6BpZQZ8Z3Z3Z3Z3Z3Z3Z3u"  # Publiczny klucz API (może wymagać aktualizacji)
  1051.         }
  1052.        
  1053.         resp = requests.get(url, params=params, headers=headers, timeout=10)
  1054.         resp.raise_for_status()
  1055.         mods = resp.json()["data"]
  1056.        
  1057.         # Cache modów
  1058.         for mod in mods:
  1059.             mod_cache[mod["id"]] = mod
  1060.        
  1061.         return mods
  1062.     except Exception as e:
  1063.         log_to_console(console, f"Błąd wyszukiwania modów: {e}", "ERROR")
  1064.         return []
  1065.  
  1066. def download_mod(mod_id, version, console):
  1067.     try:
  1068.         instance = instances.get(version)
  1069.         if not instance:
  1070.             log_to_console(console, f"Instancja {version} nie istnieje!", "ERROR")
  1071.             return False
  1072.        
  1073.         mod_dir = os.path.join(instance["path"], "mods")
  1074.         os.makedirs(mod_dir, exist_ok=True)
  1075.        
  1076.         # Pobierz informacje o modzie
  1077.         mod = mod_cache.get(mod_id)
  1078.         if not mod:
  1079.             url = f"https://api.curseforge.com/v1/mods/{mod_id}"
  1080.             headers = {
  1081.                 "x-api-key": "$2a$10$6BpZQZ8Z3Z3Z3Z3Z3Z3Z3u"  # Publiczny klucz API
  1082.             }
  1083.             resp = requests.get(url, headers=headers, timeout=10)
  1084.             resp.raise_for_status()
  1085.             mod = resp.json()["data"]
  1086.             mod_cache[mod_id] = mod
  1087.        
  1088.         # Znajdź odpowiednią wersję moda
  1089.         for file in mod["latestFiles"]:
  1090.             if instance["mod_loader"] and instance["mod_loader"]["type"]:
  1091.                 if instance["mod_loader"]["type"] == "forge" and not any("forge" in f.lower() for f in file["gameVersions"]):
  1092.                     continue
  1093.                 if instance["mod_loader"]["type"] == "fabric" and not any("fabric" in f.lower() for f in file["gameVersions"]):
  1094.                     continue
  1095.            
  1096.             if version in file["gameVersions"]:
  1097.                 mod_url = file["downloadUrl"]
  1098.                 if not mod_url:
  1099.                     mod_url = f"https://edge.forgecdn.net/files/{file['id']//{file['fileName']}"
  1100.                
  1101.                 mod_path = os.path.join(mod_dir, file["fileName"])
  1102.                 if download_file(mod_url, mod_path, None, None, console):
  1103.                     instances[version]["mods"].append({
  1104.                         "id": mod_id,
  1105.                         "name": mod["name"],
  1106.                         "file": file["fileName"],
  1107.                         "version": file["displayName"]
  1108.                     })
  1109.                     save_config()
  1110.                     return True
  1111.        
  1112.         log_to_console(console, f"Nie znaleziono odpowiedniej wersji moda {mod['name']} dla {version}", "ERROR")
  1113.         return False
  1114.     except Exception as e:
  1115.         log_to_console(console, f"Błąd pobierania moda: {e}", "ERROR")
  1116.         return False
  1117.  
  1118. def install_modpack(modpack_path, version=None):
  1119.     try:
  1120.         # Rozpakuj modpack
  1121.         with zipfile.ZipFile(modpack_path, 'r') as zip_ref:
  1122.             zip_ref.extractall(MODPACKS_DIR)
  1123.        
  1124.         # Znajdź manifest
  1125.         manifest_path = os.path.join(MODPACKS_DIR, "manifest.json")
  1126.         if not os.path.exists(manifest_path):
  1127.             log_to_console(console, "Nie znaleziono manifest.json w paczce modów!", "ERROR")
  1128.             return False
  1129.        
  1130.         with open(manifest_path, "r", encoding="utf-8") as f:
  1131.             manifest = json.load(f)
  1132.        
  1133.         # Sprawdź wersję Minecrafta
  1134.         mc_version = manifest["minecraft"]["version"]
  1135.         if version and version != mc_version:
  1136.             log_to_console(console, f"Wersja Minecrafta w paczce ({mc_version}) nie zgadza się z wybraną ({version})!", "ERROR")
  1137.             return False
  1138.        
  1139.         # Sprawdź loader modów
  1140.         loader = None
  1141.         loader_version = None
  1142.         if "forge" in manifest["minecraft"]["modLoaders"][0]["id"]:
  1143.             loader = "forge"
  1144.             loader_version = manifest["minecraft"]["modLoaders"][0]["id"].split("-")[-1]
  1145.         elif "fabric" in manifest["minecraft"]["modLoaders"][0]["id"]:
  1146.             loader = "fabric"
  1147.             loader_version = manifest["minecraft"]["modLoaders"][0]["id"].split("-")[-1]
  1148.        
  1149.         # Utwórz instancję jeśli nie istnieje
  1150.         if version not in instances:
  1151.             settings = {
  1152.                 "username": username_var.get(),
  1153.                 "memory": memory_var.get(),
  1154.                 "shared_assets": shared_assets_var.get(),
  1155.                 "shared_libraries": shared_libraries_var.get(),
  1156.                 "shared_natives": shared_natives_var.get()
  1157.             }
  1158.             download_version(mc_version, settings, console)
  1159.        
  1160.         # Zainstaluj loader jeśli potrzebny
  1161.         if loader and not instances[version].get("mod_loader"):
  1162.             install_mod_loader(mc_version, loader, loader_version, console)
  1163.        
  1164.         # Pobierz mody
  1165.         mod_dir = os.path.join(instances[version]["path"], "mods")
  1166.         os.makedirs(mod_dir, exist_ok=True)
  1167.        
  1168.         for mod in manifest["files"]:
  1169.             mod_id = mod["projectID"]
  1170.             file_id = mod["fileID"]
  1171.            
  1172.             # Pobierz informacje o pliku moda
  1173.             url = f"https://api.curseforge.com/v1/mods/{mod_id}/files/{file_id}"
  1174.             headers = {
  1175.                 "x-api-key": "$2a$10$6BpZQZ8Z3Z3Z3Z3Z3Z3Z3u"  # Publiczny klucz API
  1176.             }
  1177.             resp = requests.get(url, headers=headers, timeout=10)
  1178.             resp.raise_for_status()
  1179.             file_info = resp.json()["data"]
  1180.            
  1181.             # Pobierz mod
  1182.             mod_url = file_info["downloadUrl"]
  1183.             if not mod_url:
  1184.                 mod_url = f"https://edge.forgecdn.net/files/{file_id//1000}/{file_id % 1000}/{file_info['fileName']}"
  1185.            
  1186.             mod_path = os.path.join(mod_dir, file_info["fileName"])
  1187.             if download_file(mod_url, mod_path, None, None, console):
  1188.                 instances[version]["mods"].append({
  1189.                     "id": mod_id,
  1190.                     "name": file_info["displayName"],
  1191.                     "file": file_info["fileName"],
  1192.                     "version": file_info["fileName"]
  1193.                 })
  1194.        
  1195.         save_config()
  1196.         return True
  1197.     except Exception as e:
  1198.         log_to_console(console, f"Błąd instalacji paczki modów: {e}", "ERROR")
  1199.         return False
  1200.  
  1201. def export_instance(version):
  1202.     instance = instances.get(version)
  1203.     if not instance:
  1204.         messagebox.showerror("Błąd", f"Instancja {version} nie istnieje!")
  1205.         return
  1206.    
  1207.     export_path = filedialog.asksaveasfilename(
  1208.         defaultextension=".zip",
  1209.         filetypes=[("ZIP Archive", "*.zip")],
  1210.         initialfile=f"minecraft_{version}.zip"
  1211.     )
  1212.    
  1213.     if not export_path:
  1214.         return
  1215.    
  1216.     try:
  1217.         with zipfile.ZipFile(export_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
  1218.             for root, _, files in os.walk(instance["path"]):
  1219.                 for file in files:
  1220.                     file_path = os.path.join(root, file)
  1221.                     arcname = os.path.relpath(file_path, instance["path"])
  1222.                     zipf.write(file_path, arcname)
  1223.        
  1224.         log_to_console(console, f"Eksportowano instancję {version} do {export_path}", "SUCCESS")
  1225.         messagebox.showinfo("Sukces", f"Pomyślnie wyeksportowano instancję do {export_path}")
  1226.     except Exception as e:
  1227.         log_to_console(console, f"Błąd eksportu instancji: {e}", "ERROR")
  1228.         messagebox.showerror("Błąd", f"Nie udało się wyeksportować instancji: {e}")
  1229.  
  1230. def duplicate_instance(version):
  1231.     instance = instances.get(version)
  1232.     if not instance:
  1233.         messagebox.showerror("Błąd", f"Instancja {version} nie istnieje!")
  1234.         return
  1235.    
  1236.     new_version = f"{version}_copy"
  1237.     while new_version in instances:
  1238.         new_version = f"{new_version}_copy"
  1239.    
  1240.     new_path = os.path.join(BASE_DIR, "instances", new_version)
  1241.     try:
  1242.         shutil.copytree(instance["path"], new_path)
  1243.        
  1244.         # Zaktualizuj start.bat z nową nazwą
  1245.         with open(os.path.join(new_path, "start.bat"), "r", encoding="utf-8") as f:
  1246.             content = f.read()
  1247.         content = content.replace(version, new_version)
  1248.         with open(os.path.join(new_path, "start.bat"), "w", encoding="utf-8") as f:
  1249.             f.write(content)
  1250.        
  1251.         # Dodaj nową instancję
  1252.         instances[new_version] = {
  1253.             "path": new_path,
  1254.             "java_path": instance["java_path"],
  1255.             "java_version": instance["java_version"],
  1256.             "required_java": instance["required_java"],
  1257.             "settings": instance["settings"].copy(),
  1258.             "ready": True,
  1259.             "timestamp": datetime.now().isoformat(),
  1260.             "mod_loader": instance["mod_loader"].copy() if instance["mod_loader"] else None,
  1261.             "mods": [mod.copy() for mod in instance["mods"]] if "mods" in instance else [],
  1262.             "notes": instance.get("notes", ""),
  1263.             "play_time": 0
  1264.         }
  1265.         save_config()
  1266.        
  1267.         log_to_console(console, f"Duplikowano instancję {version} jako {new_version}", "SUCCESS")
  1268.         refresh_instances()
  1269.     except Exception as e:
  1270.         log_to_console(console, f"Błąd duplikowania instancji: {e}", "ERROR")
  1271.         messagebox.showerror("Błąd", f"Nie udało się zduplikować instancji: {e}")
  1272.  
  1273. def show_instance_context_menu(event, tree):
  1274.     item = tree.identify_row(event.y)
  1275.     if not item:
  1276.         return
  1277.    
  1278.     version = tree.item(item)["values"][0]
  1279.     instance = instances.get(version)
  1280.     if not instance:
  1281.         return
  1282.    
  1283.     menu = tk.Menu(root, tearoff=0, bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"])
  1284.    
  1285.     menu.add_command(label=f"Instancja: {version}", state="disabled")
  1286.     menu.add_separator()
  1287.     menu.add_command(label="Uruchom", command=lambda: run_game(version))
  1288.     menu.add_command(label="Edytuj", command=lambda: edit_instance(version))
  1289.     menu.add_command(label="Zmień nazwę", command=lambda: rename_instance(version))
  1290.     menu.add_command(label="Eksportuj do .zip", command=lambda: export_instance(version))
  1291.     menu.add_command(label="Otwórz folder", command=lambda: os.startfile(instance["path"]))
  1292.     menu.add_command(label="Duplikuj", command=lambda: duplicate_instance(version))
  1293.    
  1294.     if "notes" in instance and instance["notes"]:
  1295.         menu.add_separator()
  1296.         menu.add_command(label=f"Notatka: {instance['notes'][:30]}...", state="disabled")
  1297.    
  1298.     menu.tk_popup(event.x_root, event.y_root)
  1299.  
  1300. def rename_instance(old_version):
  1301.     instance = instances.get(old_version)
  1302.     if not instance:
  1303.         messagebox.showerror("Błąd", f"Instancja {old_version} nie istnieje!")
  1304.         return
  1305.    
  1306.     new_version = simpledialog.askstring("Zmień nazwę", "Nowa nazwa instancji:", initialvalue=old_version)
  1307.     if not new_version or new_version == old_version:
  1308.         return
  1309.    
  1310.     if new_version in instances:
  1311.         messagebox.showerror("Błąd", f"Instancja o nazwie {new_version} już istnieje!")
  1312.         return
  1313.    
  1314.     try:
  1315.         new_path = os.path.join(BASE_DIR, "instances", new_version)
  1316.         os.rename(instance["path"], new_path)
  1317.        
  1318.         # Zaktualizuj start.bat z nową nazwą
  1319.         with open(os.path.join(new_path, "start.bat"), "r", encoding="utf-8") as f:
  1320.             content = f.read()
  1321.         content = content.replace(old_version, new_version)
  1322.         with open(os.path.join(new_path, "start.bat"), "w", encoding="utf-8") as f:
  1323.             f.write(content)
  1324.        
  1325.         # Zaktualizuj instancję
  1326.         instances[new_version] = instance
  1327.         instances[new_version]["path"] = new_path
  1328.         del instances[old_version]
  1329.         save_config()
  1330.        
  1331.         log_to_console(console, f"Zmieniono nazwę instancji z {old_version} na {new_version}", "SUCCESS")
  1332.         refresh_instances()
  1333.     except Exception as e:
  1334.         log_to_console(console, f"Błąd zmiany nazwy instancji: {e}", "ERROR")
  1335.         messagebox.showerror("Błąd", f"Nie udało się zmienić nazwy instancji: {e}")
  1336.  
  1337. def show_mod_loader_installer(version):
  1338.     instance = instances.get(version)
  1339.     if not instance:
  1340.         messagebox.showerror("Błąd", f"Instancja {version} nie istnieje!")
  1341.         return
  1342.    
  1343.     window = tk.Toplevel(root)
  1344.     window.title(f"Instalacja mod loadera dla {version}")
  1345.     window.geometry("600x400")
  1346.     window.configure(bg=theme_colors["BG_COLOR"])
  1347.    
  1348.     frame = ttk.Frame(window)
  1349.     frame.pack(fill="both", expand=True, padx=10, pady=10)
  1350.    
  1351.     ttk.Label(frame, text=f"Wybierz mod loader dla {version}", font=("Segoe UI", 12, "bold")).pack(pady=(0, 10))
  1352.    
  1353.     loader_var = tk.StringVar(value="fabric")
  1354.    
  1355.     loaders_frame = ttk.Frame(frame)
  1356.     loaders_frame.pack(fill="x", pady=(0, 10))
  1357.    
  1358.     ttk.Radiobutton(loaders_frame, text="Fabric", variable=loader_var, value="fabric").pack(side="left", padx=5)
  1359.     ttk.Radiobutton(loaders_frame, text="Forge", variable=loader_var, value="forge").pack(side="left", padx=5)
  1360.     ttk.Radiobutton(loaders_frame, text="NeoForge", variable=loader_var, value="neoforge").pack(side="left", padx=5)
  1361.    
  1362.     versions_frame = ttk.Frame(frame)
  1363.     versions_frame.pack(fill="both", expand=True)
  1364.    
  1365.     ttk.Label(versions_frame, text="Dostępne wersje:").pack(anchor="w")
  1366.    
  1367.     versions_tree = ttk.Treeview(versions_frame, columns=("version", "type"), show="headings", height=10)
  1368.     versions_tree.heading("version", text="Wersja")
  1369.     versions_tree.heading("type", text="Typ")
  1370.     versions_tree.column("version", width=150)
  1371.     versions_tree.column("type", width=100)
  1372.    
  1373.     scrollbar = ttk.Scrollbar(versions_frame, orient="vertical", command=versions_tree.yview)
  1374.     scrollbar.pack(side="right", fill="y")
  1375.     versions_tree.configure(yscrollcommand=scrollbar.set)
  1376.     versions_tree.pack(fill="both", expand=True)
  1377.    
  1378.     def refresh_versions():
  1379.         loader = loader_var.get()
  1380.         versions_tree.delete(*versions_tree.get_children())
  1381.        
  1382.         if loader not in mod_loader_versions or not mod_loader_versions[loader]:
  1383.             log_to_console(console, f"Pobieranie wersji {loader}...", "INFO")
  1384.             versions = get_mod_loader_versions(loader, version)
  1385.             mod_loader_versions[loader] = versions
  1386.             save_config()
  1387.         else:
  1388.             versions = mod_loader_versions[loader]
  1389.        
  1390.         if loader == "fabric":
  1391.             for ver in versions:
  1392.                 if isinstance(ver, dict) and ver["loader"]:
  1393.                     versions_tree.insert("", "end", values=(ver["loader"]["version"], "Loader"))
  1394.                 elif isinstance(ver, dict) and ver["version"]:
  1395.                     versions_tree.insert("", "end", values=(ver["version"], "API"))
  1396.         elif loader == "forge":
  1397.             for ver in versions:
  1398.                 if ver["mc_version"] == version:
  1399.                     versions_tree.insert("", "end", values=(ver["loader_version"], "Latest"))
  1400.                     if ver["recommended"]:
  1401.                         versions_tree.insert("", "end", values=(ver["recommended"], "Recommended"))
  1402.         elif loader == "neoforge":
  1403.             versions_tree.insert("", "end", values=(versions, "Latest"))
  1404.    
  1405.     refresh_versions()
  1406.    
  1407.     btn_frame = ttk.Frame(frame)
  1408.     btn_frame.pack(fill="x", pady=(10, 0))
  1409.    
  1410.     ttk.Button(btn_frame, text="Odśwież", command=refresh_versions).pack(side="left", padx=5)
  1411.     ttk.Button(btn_frame, text="Zainstaluj", command=lambda: install_selected_version()).pack(side="right", padx=5)
  1412.    
  1413.     def install_selected_version():
  1414.         selected = versions_tree.selection()
  1415.         if not selected:
  1416.             messagebox.showwarning("Uwaga", "Wybierz wersję do instalacji!")
  1417.             return
  1418.        
  1419.         loader_version = versions_tree.item(selected[0])["values"][0]
  1420.         loader = loader_var.get()
  1421.        
  1422.         if install_mod_loader(version, loader, loader_version, console):
  1423.             messagebox.showinfo("Sukces", f"Pomyślnie zainstalowano {loader.capitalize()} {loader_version}!")
  1424.             window.destroy()
  1425.             refresh_instances()
  1426.         else:
  1427.             messagebox.showerror("Błąd", f"Nie udało się zainstalować {loader.capitalize()}!")
  1428.  
  1429. def show_mod_browser():
  1430.     window = tk.Toplevel(root)
  1431.     window.title("Przeglądarka modów")
  1432.     window.geometry("800x600")
  1433.     window.configure(bg=theme_colors["BG_COLOR"])
  1434.    
  1435.     frame = ttk.Frame(window)
  1436.     frame.pack(fill="both", expand=True, padx=10, pady=10)
  1437.    
  1438.     search_frame = ttk.Frame(frame)
  1439.     search_frame.pack(fill="x", pady=(0, 10))
  1440.    
  1441.     ttk.Label(search_frame, text="Wyszukaj mody:").pack(side="left", padx=5)
  1442.    
  1443.     search_var = tk.StringVar()
  1444.     search_entry = ttk.Entry(search_frame, textvariable=search_var)
  1445.     search_entry.pack(side="left", fill="x", expand=True, padx=5)
  1446.    
  1447.     def perform_search():
  1448.         query = search_var.get()
  1449.         if not query:
  1450.             return
  1451.        
  1452.         mc_version = version_combo.get()
  1453.         loader = loader_combo.get()
  1454.        
  1455.         mods = search_mods(query, mc_version, loader)
  1456.         mods_tree.delete(*mods_tree.get_children())
  1457.        
  1458.         for mod in mods:
  1459.             mods_tree.insert("", "end", values=(
  1460.                 mod["name"],
  1461.                 mod["summary"][:50] + "..." if mod["summary"] else "",
  1462.                 ", ".join(mod["authors"][:3]) if mod["authors"] else "",
  1463.                 mod["downloadCount"]
  1464.             ))
  1465.    
  1466.     ttk.Button(search_frame, text="Szukaj", command=perform_search).pack(side="left", padx=5)
  1467.    
  1468.     filters_frame = ttk.Frame(frame)
  1469.     filters_frame.pack(fill="x", pady=(0, 10))
  1470.    
  1471.     ttk.Label(filters_frame, text="Wersja MC:").pack(side="left", padx=5)
  1472.     version_combo = ttk.Combobox(filters_frame, values=list(instances.keys()), state="readonly")
  1473.     version_combo.pack(side="left", padx=5)
  1474.     if instances:
  1475.         version_combo.current(0)
  1476.    
  1477.     ttk.Label(filters_frame, text="Mod loader:").pack(side="left", padx=5)
  1478.     loader_combo = ttk.Combobox(filters_frame, values=["Fabric", "Forge", "Any"], state="readonly")
  1479.     loader_combo.pack(side="left", padx=5)
  1480.     loader_combo.current(2)
  1481.    
  1482.     mods_tree = ttk.Treeview(frame, columns=("name", "description", "authors", "downloads"), show="headings", height=15)
  1483.     mods_tree.heading("name", text="Nazwa")
  1484.     mods_tree.heading("description", text="Opis")
  1485.     mods_tree.heading("authors", text="Autorzy")
  1486.     mods_tree.heading("downloads", text="Pobrań")
  1487.     mods_tree.column("name", width=200)
  1488.     mods_tree.column("description", width=250)
  1489.     mods_tree.column("authors", width=150)
  1490.     mods_tree.column("downloads", width=80)
  1491.    
  1492.     scrollbar = ttk.Scrollbar(frame, orient="vertical", command=mods_tree.yview)
  1493.     scrollbar.pack(side="right", fill="y")
  1494.     mods_tree.configure(yscrollcommand=scrollbar.set)
  1495.     mods_tree.pack(fill="both", expand=True)
  1496.    
  1497.     btn_frame = ttk.Frame(frame)
  1498.     btn_frame.pack(fill="x", pady=(10, 0))
  1499.    
  1500.     def install_selected_mod():
  1501.         selected = mods_tree.selection()
  1502.         if not selected:
  1503.             messagebox.showwarning("Uwaga", "Wybierz mod do instalacji!")
  1504.             return
  1505.        
  1506.         mod_name = mods_tree.item(selected[0])["values"][0]
  1507.         mc_version = version_combo.get()
  1508.        
  1509.         # Znajdź ID moda w cache
  1510.         mod_id = None
  1511.         for id, mod in mod_cache.items():
  1512.             if mod["name"] == mod_name:
  1513.                 mod_id = id
  1514.                 break
  1515.        
  1516.         if not mod_id:
  1517.             messagebox.showerror("Błąd", "Nie znaleziono wybranego moda!")
  1518.             return
  1519.        
  1520.         if download_mod(mod_id, mc_version, console):
  1521.             messagebox.showinfo("Sukces", f"Pomyślnie zainstalowano mod {mod_name}!")
  1522.         else:
  1523.             messagebox.showerror("Błąd", f"Nie udało się zainstalować moda {mod_name}!")
  1524.    
  1525.     ttk.Button(btn_frame, text="Zainstaluj", command=install_selected_mod).pack(side="right", padx=5)
  1526.     ttk.Button(btn_frame, text="Pokaż szczegóły", command=lambda: show_mod_details()).pack(side="right", padx=5)
  1527.    
  1528.     def show_mod_details():
  1529.         selected = mods_tree.selection()
  1530.         if not selected:
  1531.             return
  1532.        
  1533.         mod_name = mods_tree.item(selected[0])["values"][0]
  1534.        
  1535.         # Znajdź mod w cache
  1536.         mod = None
  1537.         for m in mod_cache.values():
  1538.             if m["name"] == mod_name:
  1539.                 mod = m
  1540.                 break
  1541.        
  1542.         if not mod:
  1543.             return
  1544.        
  1545.         details_window = tk.Toplevel(window)
  1546.         details_window.title(f"Szczegóły: {mod_name}")
  1547.         details_window.geometry("600x400")
  1548.         details_window.configure(bg=theme_colors["BG_COLOR"])
  1549.        
  1550.         frame = ttk.Frame(details_window)
  1551.         frame.pack(fill="both", expand=True, padx=10, pady=10)
  1552.        
  1553.         ttk.Label(frame, text=mod_name, font=("Segoe UI", 14, "bold")).pack(pady=(0, 10))
  1554.        
  1555.         desc_frame = ttk.Frame(frame)
  1556.         desc_frame.pack(fill="x", pady=(0, 10))
  1557.        
  1558.         ttk.Label(desc_frame, text="Opis:", font=("Segoe UI", 10, "bold")).pack(anchor="w")
  1559.         desc_text = scrolledtext.ScrolledText(
  1560.             desc_frame, wrap=tk.WORD, height=10,
  1561.             bg=theme_colors["ENTRY_BG"], fg=theme_colors["FG_COLOR"]
  1562.         )
  1563.         desc_text.insert(tk.END, mod["summary"] or "Brak opisu")
  1564.         desc_text.config(state="disabled")
  1565.         desc_text.pack(fill="both", expand=True)
  1566.        
  1567.         info_frame = ttk.Frame(frame)
  1568.         info_frame.pack(fill="x", pady=(0, 10))
  1569.        
  1570.         ttk.Label(info_frame, text=f"Autorzy: {', '.join([a['name'] for a in mod['authors']]) if mod['authors'] else 'Brak informacji'}").pack(anchor="w")
  1571.         ttk.Label(info_frame, text=f"Pobrań: {humanize.intcomma(mod['downloadCount'])}").pack(anchor="w")
  1572.         ttk.Label(info_frame, text=f"Data wydania: {mod['dateReleased']}").pack(anchor="w")
  1573.        
  1574.         btn_frame = ttk.Frame(frame)
  1575.         btn_frame.pack(fill="x", pady=(10, 0))
  1576.        
  1577.         ttk.Button(btn_frame, text="Strona projektu", command=lambda: webbrowser.open(mod["links"]["websiteUrl"])).pack(side="left", padx=5)
  1578.  
  1579. def show_modpack_installer():
  1580.     window = tk.Toplevel(root)
  1581.     window.title("Instalator paczek modów")
  1582.     window.geometry("600x400")
  1583.     window.configure(bg=theme_colors["BG_COLOR"])
  1584.    
  1585.     frame = ttk.Frame(window)
  1586.     frame.pack(fill="both", expand=True, padx=10, pady=10)
  1587.    
  1588.     ttk.Label(frame, text="Wybierz paczkę modów do instalacji", font=("Segoe UI", 12, "bold")).pack(pady=(0, 10))
  1589.    
  1590.     options_frame = ttk.Frame(frame)
  1591.     options_frame.pack(fill="x", pady=(0, 10))
  1592.    
  1593.     ttk.Radiobutton(options_frame, text="Z pliku (.zip)", value="file", variable=tk.StringVar(value="file")).pack(anchor="w")
  1594.     ttk.Radiobutton(options_frame, text="Z URL", value="url", variable=tk.StringVar(value="file")).pack(anchor="w")
  1595.    
  1596.     file_frame = ttk.Frame(frame)
  1597.     file_frame.pack(fill="x", pady=(0, 10))
  1598.    
  1599.     ttk.Button(file_frame, text="Wybierz plik...", command=lambda: select_modpack_file()).pack(side="left", padx=5)
  1600.     file_path_var = tk.StringVar()
  1601.     ttk.Label(file_frame, textvariable=file_path_var).pack(side="left", padx=5)
  1602.    
  1603.     url_frame = ttk.Frame(frame)
  1604.     url_frame.pack(fill="x", pady=(0, 10))
  1605.    
  1606.     ttk.Label(url_frame, text="URL:").pack(side="left", padx=5)
  1607.     url_entry = ttk.Entry(url_frame)
  1608.     url_entry.pack(side="left", fill="x", expand=True, padx=5)
  1609.    
  1610.     version_frame = ttk.Frame(frame)
  1611.     version_frame.pack(fill="x", pady=(0, 10))
  1612.    
  1613.     ttk.Label(version_frame, text="Dla wersji MC:").pack(side="left", padx=5)
  1614.     version_combo = ttk.Combobox(version_frame, values=list(instances.keys()), state="readonly")
  1615.     version_combo.pack(side="left", fill="x", expand=True, padx=5)
  1616.     if instances:
  1617.         version_combo.current(0)
  1618.    
  1619.     btn_frame = ttk.Frame(frame)
  1620.     btn_frame.pack(fill="x", pady=(10, 0))
  1621.    
  1622.     def select_modpack_file():
  1623.         file_path = filedialog.askopenfilename(
  1624.             filetypes=[("ZIP Archive", "*.zip")],
  1625.             title="Wybierz paczkę modów"
  1626.         )
  1627.         if file_path:
  1628.             file_path_var.set(file_path)
  1629.    
  1630.     def install_modpack():
  1631.         mc_version = version_combo.get()
  1632.         if not mc_version:
  1633.             messagebox.showwarning("Uwaga", "Wybierz wersję Minecrafta!")
  1634.             return
  1635.        
  1636.         if install_modpack(file_path_var.get() if file_path_var.get() else url_entry.get(), mc_version):
  1637.             messagebox.showinfo("Sukces", "Pomyślnie zainstalowano paczkę modów!")
  1638.             window.destroy()
  1639.             refresh_instances()
  1640.         else:
  1641.             messagebox.showerror("Błąd", "Nie udało się zainstalować paczki modów!")
  1642.    
  1643.     ttk.Button(btn_frame, text="Zainstaluj", command=install_modpack).pack(side="right", padx=5)
  1644.  
  1645. def show_instance_stats(version):
  1646.     instance = instances.get(version)
  1647.     if not instance:
  1648.         messagebox.showerror("Błąd", f"Instancja {version} nie istnieje!")
  1649.         return
  1650.    
  1651.     window = tk.Toplevel(root)
  1652.     window.title(f"Statystyki instancji {version}")
  1653.     window.geometry("400x300")
  1654.     window.configure(bg=theme_colors["BG_COLOR"])
  1655.    
  1656.     frame = ttk.Frame(window)
  1657.     frame.pack(fill="both", expand=True, padx=10, pady=10)
  1658.    
  1659.     ttk.Label(frame, text=f"Statystyki: {version}", font=("Segoe UI", 14, "bold")).pack(pady=(0, 10))
  1660.    
  1661.     stats_frame = ttk.Frame(frame)
  1662.     stats_frame.pack(fill="both", expand=True)
  1663.    
  1664.     # Oblicz rozmiar instancji
  1665.     total_size = 0
  1666.     for dirpath, _, filenames in os.walk(instance["path"]):
  1667.         for f in filenames:
  1668.             fp = os.path.join(dirpath, f)
  1669.             total_size += os.path.getsize(fp)
  1670.    
  1671.     # Oblicz ilość modów
  1672.     mods_count = len(instance.get("mods", []))
  1673.    
  1674.     # Czas grania
  1675.     play_time = instance.get("play_time", 0)
  1676.     play_time_str = f"{int(play_time // 3600)}h {int((play_time % 3600) // 60)}m"
  1677.    
  1678.     ttk.Label(stats_frame, text=f"Wersja Minecrafta: {version}", font=("Segoe UI", 10)).pack(anchor="w", pady=2)
  1679.     ttk.Label(stats_frame, text=f"Mod loader: {instance['mod_loader']['type'].capitalize() + ' ' + instance['mod_loader']['version'] if instance['mod_loader'] else 'Brak'}", font=("Segoe UI", 10)).pack(anchor="w", pady=2)
  1680.     ttk.Label(stats_frame, text=f"Ilość modów: {mods_count}", font=("Segoe UI", 10)).pack(anchor="w", pady=2)
  1681.     ttk.Label(stats_frame, text=f"Rozmiar instancji: {humanize.naturalsize(total_size)}", font=("Segoe UI", 10)).pack(anchor="w", pady=2)
  1682.     ttk.Label(stats_frame, text=f"Wersja Javy: {instance['java_version']}", font=("Segoe UI", 10)).pack(anchor="w", pady=2)
  1683.     ttk.Label(stats_frame, text=f"Data utworzenia: {datetime.fromisoformat(instance['timestamp']).strftime('%Y-%m-%d %H:%M')}", font=("Segoe UI", 10)).pack(anchor="w", pady=2)
  1684.     ttk.Label(stats_frame, text=f"Czas grania: {play_time_str}", font=("Segoe UI", 10)).pack(anchor="w", pady=2)
  1685.    
  1686.     if instance.get("notes", ""):
  1687.         notes_frame = ttk.Frame(frame)
  1688.         notes_frame.pack(fill="x", pady=(10, 0))
  1689.        
  1690.         ttk.Label(notes_frame, text="Notatki:", font=("Segoe UI", 10, "bold")).pack(anchor="w")
  1691.         notes_text = scrolledtext.ScrolledText(
  1692.             notes_frame, wrap=tk.WORD, height=4,
  1693.             bg=theme_colors["ENTRY_BG"], fg=theme_colors["FG_COLOR"]
  1694.         )
  1695.         notes_text.insert(tk.END, instance["notes"])
  1696.         notes_text.config(state="disabled")
  1697.         notes_text.pack(fill="both", expand=True)
  1698.  
  1699. def add_instance_note(version):
  1700.     instance = instances.get(version)
  1701.     if not instance:
  1702.         messagebox.showerror("Błąd", f"Instancja {version} nie istnieje!")
  1703.         return
  1704.    
  1705.     note = simpledialog.askstring("Dodaj notatkę", "Wpisz notatkę dla tej instancji:", initialvalue=instance.get("notes", ""))
  1706.     if note is not None:
  1707.         instance["notes"] = note
  1708.         save_config()
  1709.         log_to_console(console, f"Dodano notatkę do instancji {version}", "SUCCESS")
  1710.  
  1711. if __name__ == "__main__":
  1712.     try:
  1713.         print("Inicjalizacja Tkinter...")
  1714.         root = tk.Tk()
  1715.         print("Tkinter zainicjalizowany.")
  1716.         root.title("Minecraft Launcher by Paffcio")
  1717.         root.geometry("1000x750")
  1718.         root.minsize(900, 650)
  1719.  
  1720.         print("Ładowanie konfiguracji...")
  1721.         # Inicjalizacja zmiennych Tkinter przed ładowaniem konfiguracji
  1722.         username_var = tk.StringVar(value="Player")
  1723.         memory_var = tk.StringVar(value="2")
  1724.         shared_assets_var = tk.BooleanVar(value=True)
  1725.         shared_libraries_var = tk.BooleanVar(value=True)
  1726.         shared_natives_var = tk.BooleanVar(value=True)
  1727.         snapshots_var = tk.BooleanVar(value=True)
  1728.         releases_var = tk.BooleanVar(value=True)
  1729.         alpha_var = tk.BooleanVar(value=False)
  1730.         beta_var = tk.BooleanVar(value=False)
  1731.         current_tab = tk.StringVar(value="Instancje")
  1732.        
  1733.         # Teraz ładujemy konfigurację, która zaktualizuje zmienne
  1734.         instances, java_versions_cache = load_config()
  1735.         print("Konfiguracja wczytana.")
  1736.  
  1737.         print("Inicjalizacja UI...")
  1738.         apply_theme()
  1739.  
  1740.         progress_frame = ttk.Frame(root)
  1741.         progress_frame.pack(side="bottom", fill="x", padx=10, pady=5)
  1742.         global_progress_bar = ttk.Progressbar(progress_frame, length=400, mode="determinate", style="Horizontal.TProgressbar")
  1743.         global_progress_bar.pack(fill="x")
  1744.         global_status_label = ttk.Label(progress_frame, text="Gotowe do działania!", foreground=theme_colors["FG_COLOR"], background=theme_colors["BG_COLOR"], font=("Segoe UI", 9))
  1745.         global_status_label.pack()
  1746.  
  1747.         sidebar = tk.Frame(root, bg=theme_colors["SIDEBAR_BG"], width=220)
  1748.         sidebar.pack(side="left", fill="y", padx=0, pady=0)
  1749.  
  1750.         logo_frame = tk.Frame(sidebar, bg=theme_colors["SIDEBAR_BG"])
  1751.         logo_frame.pack(fill="x", pady=(10, 20))
  1752.         try:
  1753.             print("Ładowanie logo...")
  1754.             logo_img = Image.open(os.path.join(ICONS_DIR, "logo.png")) if os.path.exists(ICONS_DIR) else None
  1755.             if logo_img:
  1756.                 logo_img = logo_img.resize((180, 60), Image.LANCZOS)
  1757.                 logo_photo = ImageTk.PhotoImage(logo_img)
  1758.                 logo_label = tk.Label(logo_frame, image=logo_photo, bg=theme_colors["SIDEBAR_BG"])
  1759.                 logo_label.image = logo_photo
  1760.                 logo_label.pack(pady=5)
  1761.             print("Logo załadowane.")
  1762.         except Exception as e:
  1763.             print(f"Błąd ładowania logo: {e}")
  1764.  
  1765.         tabs = [
  1766.             ("Instancje", "📋"),
  1767.             ("Pobieranie", "⬇"),
  1768.             ("Mod Loadery", "⚙"),
  1769.             ("Przeglądarka modów", "🌐"),
  1770.             ("Paczki modów", "📦"),
  1771.             ("Ustawienia", "⚙"),
  1772.             ("Konsola", "📜")
  1773.         ]
  1774.         tab_buttons = {}
  1775.         for tab_name, icon in tabs:
  1776.             print(f"Tworzenie przycisku {tab_name}...")
  1777.             btn = tk.Button(
  1778.                 sidebar, text=f"  {icon} {tab_name}", bg=theme_colors["SIDEBAR_BG"], fg=theme_colors["FG_COLOR"],
  1779.                 activebackground=theme_colors["ACTIVE_TAB_COLOR"], activeforeground=theme_colors["FG_COLOR"],
  1780.                 font=("Segoe UI", 11), relief="flat", anchor="w", padx=15, pady=10,
  1781.                 command=lambda t=tab_name: switch_tab(t)
  1782.             )
  1783.             btn.pack(fill="x", pady=0)
  1784.             tab_buttons[tab_name] = btn
  1785.             btn.bind("<Enter>", lambda e, b=btn: b.config(bg=theme_colors["HOVER_TAB_COLOR"]) if current_tab.get() != tab_name else None)
  1786.             btn.bind("<Leave>", lambda e, b=btn: b.config(bg=theme_colors["SIDEBAR_BG"]) if current_tab.get() != tab_name else None)
  1787.             print(f"Przycisk {tab_name} utworzony.")
  1788.  
  1789.         content_frame = ttk.Frame(root)
  1790.         content_frame.pack(side="left", fill="both", expand=True, padx=10, pady=10)
  1791.  
  1792.         def switch_tab(tab_name):
  1793.             global download_active
  1794.             if download_active and tab_name != "Pobieranie":
  1795.                 if not messagebox.askyesno("Ostrzeżenie", "Pobieranie w toku! Zmiana zakładki przerwie proces. Kontynuować?"):
  1796.                     return
  1797.                 download_active = False
  1798.             current_tab.set(tab_name)
  1799.             for name, btn in tab_buttons.items():
  1800.                 if name == tab_name:
  1801.                     btn.config(bg=theme_colors["ACTIVE_TAB_COLOR"], relief="sunken")
  1802.                 else:
  1803.                     btn.config(bg=theme_colors["SIDEBAR_BG"], relief="flat")
  1804.             for widget in content_frame.winfo_children():
  1805.                 widget.destroy()
  1806.  
  1807.             if tab_name == "Instancje":
  1808.                 show_instances()
  1809.             elif tab_name == "Pobieranie":
  1810.                 show_download()
  1811.             elif tab_name == "Mod Loadery":
  1812.                 show_mod_loader_tab()
  1813.             elif tab_name == "Przeglądarka modów":
  1814.                 show_mod_browser()
  1815.             elif tab_name == "Paczki modów":
  1816.                 show_modpack_installer()
  1817.             elif tab_name == "Ustawienia":
  1818.                 show_settings()
  1819.             elif tab_name == "Konsola":
  1820.                 show_console()
  1821.  
  1822.         def show_instances():
  1823.             frame = ttk.Frame(content_frame)
  1824.             frame.pack(fill="both", expand=True)
  1825.  
  1826.             header_frame = ttk.Frame(frame)
  1827.             header_frame.pack(fill="x", pady=(0, 10))
  1828.             ttk.Label(header_frame, text="Twoje instancje Minecraft", font=("Segoe UI", 14, "bold")).pack(side="left")
  1829.            
  1830.             btn_frame = ttk.Frame(header_frame)
  1831.             btn_frame.pack(side="right")
  1832.            
  1833.             refresh_btn = ttk.Button(btn_frame, text="Odśwież", command=refresh_instances, width=10)
  1834.             refresh_btn.pack(side="left", padx=5)
  1835.            
  1836.             new_btn = ttk.Button(btn_frame, text="Nowa instancja", command=create_instance, width=15)
  1837.             new_btn.pack(side="left", padx=5)
  1838.  
  1839.             tree_frame = ttk.Frame(frame)
  1840.             tree_frame.pack(fill="both", expand=True)
  1841.            
  1842.             columns = ("version", "java", "status", "date", "mod_loader", "mods")
  1843.             tree = ttk.Treeview(
  1844.                 tree_frame, columns=columns, show="headings", selectmode="browse",
  1845.                 style="Treeview"
  1846.             )
  1847.            
  1848.             tree.heading("version", text="Wersja", anchor="w")
  1849.             tree.heading("java", text="Java", anchor="w")
  1850.             tree.heading("status", text="Status", anchor="w")
  1851.             tree.heading("date", text="Data utworzenia", anchor="w")
  1852.             tree.heading("mod_loader", text="Mod Loader", anchor="w")
  1853.             tree.heading("mods", text="Mody", anchor="w")
  1854.            
  1855.             tree.column("version", width=150, anchor="w")
  1856.             tree.column("java", width=100, anchor="w")
  1857.             tree.column("status", width=80, anchor="w")
  1858.             tree.column("date", width=120, anchor="w")
  1859.             tree.column("mod_loader", width=120, anchor="w")
  1860.             tree.column("mods", width=80, anchor="w")
  1861.            
  1862.             scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=tree.yview)
  1863.             scrollbar.pack(side="right", fill="y")
  1864.             tree.configure(yscrollcommand=scrollbar.set)
  1865.             tree.pack(fill="both", expand=True)
  1866.            
  1867.             for version, data in instances.items():
  1868.                 tree.insert("", "end", values=(
  1869.                     version,
  1870.                     data.get("java_version", "?"),
  1871.                     "Gotowe" if data.get("ready", False) else "Błąd",
  1872.                     datetime.fromisoformat(data["timestamp"]).strftime("%Y-%m-%d %H:%M"),
  1873.                     f"{data['mod_loader']['type'].capitalize()} {data['mod_loader']['version']}" if data.get("mod_loader") else "Brak",
  1874.                     len(data.get("mods", []))
  1875.                 ))
  1876.            
  1877.             # Menu kontekstowe
  1878.             tree.bind("<Button-3>", lambda e: show_instance_context_menu(e, tree))
  1879.            
  1880.             action_frame = ttk.Frame(frame)
  1881.             action_frame.pack(fill="x", pady=(10, 0))
  1882.            
  1883.             run_btn = ttk.Button(action_frame, text="Uruchom", command=lambda: run_game(tree.item(tree.selection())["values"][0] if tree.selection() else ""), width=15)
  1884.             run_btn.pack(side="left", padx=5)
  1885.            
  1886.             edit_btn = ttk.Button(action_frame, text="Edytuj", command=lambda: edit_instance(tree.item(tree.selection())["values"][0] if tree.selection() else ""), width=15)
  1887.             edit_btn.pack(side="left", padx=5)
  1888.            
  1889.             verify_btn = ttk.Button(action_frame, text="Sprawdź", command=lambda: verify_instance(tree.item(tree.selection())["values"][0] if tree.selection() else "", console), width=15)
  1890.             verify_btn.pack(side="left", padx=5)
  1891.            
  1892.             delete_btn = ttk.Button(action_frame, text="Usuń", command=lambda: delete_instance(tree.item(tree.selection())["values"][0] if tree.selection() else ""), width=15)
  1893.             delete_btn.pack(side="left", padx=5)
  1894.            
  1895.             ttk.Label(frame, text="Wybierz instancję i kliknij odpowiedni przycisk", font=("Segoe UI", 9)).pack(pady=(10, 0))
  1896.  
  1897.         def refresh_instances():
  1898.             for widget in content_frame.winfo_children():
  1899.                 widget.destroy()
  1900.             show_instances()
  1901.  
  1902.         def create_instance():
  1903.             window = tk.Toplevel(root)
  1904.             window.title("Nowa instancja Minecraft")
  1905.             window.geometry("500x650")
  1906.             window.resizable(False, False)
  1907.            
  1908.             style.configure("NewInstance.TFrame", background=theme_colors["BG_COLOR"])
  1909.             style.configure("NewInstance.TLabel", background=theme_colors["BG_COLOR"], foreground=theme_colors["FG_COLOR"], font=("Segoe UI", 10))
  1910.             style.configure("NewInstance.TButton", background=theme_colors["BUTTON_BG"], foreground=theme_colors["FG_COLOR"], font=("Segoe UI", 10))
  1911.  
  1912.             frame = ttk.Frame(window, style="NewInstance.TFrame", padding="15")
  1913.             frame.pack(fill="both", expand=True)
  1914.            
  1915.             ttk.Label(frame, text="Tworzenie nowej instancji", font=("Segoe UI", 14, "bold")).pack(pady=(0, 15))
  1916.            
  1917.             form_frame = ttk.Frame(frame)
  1918.             form_frame.pack(fill="both", expand=True)
  1919.            
  1920.             ttk.Label(form_frame, text="Filtry wersji:").grid(row=0, column=0, sticky="w", pady=(0, 5))
  1921.             filters_frame = ttk.Frame(form_frame)
  1922.             filters_frame.grid(row=0, column=1, sticky="w", pady=(0, 5))
  1923.            
  1924.             tk.Checkbutton(filters_frame, text="Snapshoty", variable=snapshots_var, bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"],
  1925.                            selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"], font=("Segoe UI", 9),
  1926.                            command=lambda: [save_config(), refresh_version_combo(version_combo, version_var)]).pack(anchor="w", pady=2)
  1927.             tk.Checkbutton(filters_frame, text="Release", variable=releases_var, bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"],
  1928.                            selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"], font=("Segoe UI", 9),
  1929.                            command=lambda: [save_config(), refresh_version_combo(version_combo, version_var)]).pack(anchor="w", pady=2)
  1930.             tk.Checkbutton(filters_frame, text="Alpha", variable=alpha_var, bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"],
  1931.                            selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"], font=("Segoe UI", 9),
  1932.                            command=lambda: [save_config(), refresh_version_combo(version_combo, version_var)]).pack(anchor="w", pady=2)
  1933.             tk.Checkbutton(filters_frame, text="Beta", variable=beta_var, bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"],
  1934.                            selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"], font=("Segoe UI", 9),
  1935.                            command=lambda: [save_config(), refresh_version_combo(version_combo, version_var)]).pack(anchor="w", pady=2)
  1936.            
  1937.             ttk.Label(form_frame, text="Wersja Minecrafta:").grid(row=1, column=0, sticky="w", pady=(10, 5))
  1938.             version_var = tk.StringVar()
  1939.             versions = get_versions()
  1940.             version_combo = ttk.Combobox(form_frame, textvariable=version_var, state="readonly", values=versions)
  1941.             version_combo.grid(row=1, column=1, sticky="ew", pady=(10, 5), padx=(5, 0))
  1942.             if versions:
  1943.                 version_combo.current(0)
  1944.             else:
  1945.                 messagebox.showwarning("Uwaga", "Włącz przynajmniej jeden filtr wersji (Snapshoty, Release, Alpha, Beta)!")
  1946.            
  1947.             ttk.Label(form_frame, text="Nazwa użytkownika:").grid(row=2, column=0, sticky="w", pady=(10, 5))
  1948.             username_entry = ttk.Entry(form_frame)
  1949.             username_entry.insert(0, username_var.get())
  1950.             username_entry.grid(row=2, column=1, sticky="ew", pady=(10, 5), padx=(5, 0))
  1951.            
  1952.             ttk.Label(form_frame, text="Pamięć RAM (GB):").grid(row=3, column=0, sticky="w", pady=(10, 5))
  1953.             memory_spin = tk.Spinbox(form_frame, from_=1, to=16, width=5, bg=theme_colors["ENTRY_BG"], fg=theme_colors["FG_COLOR"], highlightthickness=0)
  1954.             memory_spin.delete(0, tk.END)
  1955.             memory_spin.insert(0, memory_var.get())
  1956.             memory_spin.grid(row=3, column=1, sticky="w", pady=(10, 5), padx=(5, 0))
  1957.            
  1958.             ttk.Label(form_frame, text="Ścieżka Java:").grid(row=4, column=0, sticky="w", pady=(10, 5))
  1959.             java_var = tk.StringVar()
  1960.             java_combo = ttk.Combobox(form_frame, textvariable=java_var, state="readonly")
  1961.             java_paths = find_java()
  1962.             java_combo['values'] = [f"{p} ({v})" for p, v in java_paths]
  1963.             java_combo.grid(row=4, column=1, sticky="ew", pady=(10, 5), padx=(5, 0))
  1964.            
  1965.             # Automatyczny wybór najlepszej Javy
  1966.             if versions and java_paths:
  1967.                 version_info = get_version_info(version_var.get())
  1968.                 required_java = get_required_java(version_var.get(), version_info)
  1969.                 best_java_index = 0
  1970.                 for i, (path, ver) in enumerate(java_paths):
  1971.                     if check_java_version(ver, required_java):
  1972.                         best_java_index = i
  1973.                         break
  1974.                 java_combo.current(best_java_index)
  1975.            
  1976.             # Aktualizacja Javy przy zmianie wersji Minecrafta
  1977.             def update_java_combo(event):
  1978.                 version_info = get_version_info(version_var.get())
  1979.                 required_java = get_required_java(version_var.get(), version_info)
  1980.                 for i, (path, ver) in enumerate(java_paths):
  1981.                     if check_java_version(ver, required_java):
  1982.                         java_combo.current(i)
  1983.                         break
  1984.                 else:
  1985.                     java_combo.current(0) if java_paths else java_combo.set("Brak Javy")
  1986.            
  1987.             version_combo.bind("<<ComboboxSelected>>", update_java_combo)
  1988.            
  1989.             options_frame = ttk.Frame(form_frame)
  1990.             options_frame.grid(row=5, column=0, columnspan=2, sticky="ew", pady=(10, 5))
  1991.            
  1992.             shared_assets = tk.BooleanVar(value=shared_assets_var.get())
  1993.             shared_libs = tk.BooleanVar(value=shared_libraries_var.get())
  1994.             shared_natives = tk.BooleanVar(value=shared_natives_var.get())
  1995.            
  1996.             tk.Checkbutton(options_frame, text="Współdziel assets", variable=shared_assets,
  1997.                            bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"], selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"],
  1998.                            font=("Segoe UI", 9)).pack(anchor="w", pady=2)
  1999.             tk.Checkbutton(options_frame, text="Współdziel biblioteki", variable=shared_libs,
  2000.                            bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"], selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"],
  2001.                            font=("Segoe UI", 9)).pack(anchor="w", pady=2)
  2002.             tk.Checkbutton(options_frame, text="Współdziel natywne biblioteki", variable=shared_natives,
  2003.                            bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"], selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"],
  2004.                            font=("Segoe UI", 9)).pack(anchor="w", pady=2)
  2005.            
  2006.             btn_frame = ttk.Frame(frame)
  2007.             btn_frame.pack(fill="x", pady=(15, 0))
  2008.            
  2009.             ttk.Button(btn_frame, text="Anuluj", command=window.destroy, width=15).pack(side="right", padx=5)
  2010.             ttk.Button(btn_frame, text="Utwórz", command=lambda: [
  2011.                 save_pending_instance({
  2012.                     "username": username_entry.get(),
  2013.                     "memory": memory_spin.get(),
  2014.                     "java_path": java_paths[int(java_combo.current())][0] if java_combo.current() != -1 else "",
  2015.                     "shared_assets": shared_assets.get(),
  2016.                     "shared_libraries": shared_libs.get(),
  2017.                     "shared_natives": shared_natives.get()
  2018.                 }, version_var.get()),
  2019.                 switch_tab("Pobieranie"),
  2020.                 window.destroy()
  2021.             ] if version_var.get() else messagebox.showwarning("Uwaga", "Wybierz wersję!")).pack(side="right", padx=5)
  2022.  
  2023.         def save_pending_instance(settings, version):
  2024.             global pending_instance_settings, pending_version
  2025.             pending_instance_settings = settings
  2026.             pending_version = version
  2027.  
  2028.         def edit_instance(version):
  2029.             if not version or version not in instances:
  2030.                 messagebox.showwarning("Uwaga", "Wybierz instancję!")
  2031.                 return
  2032.             instance = instances[version]
  2033.             settings = instance.get("settings", {})
  2034.  
  2035.             window = tk.Toplevel(root)
  2036.             window.title(f"Edytuj instancję {version}")
  2037.             window.geometry("500x550")
  2038.             window.resizable(False, False)
  2039.  
  2040.             frame = ttk.Frame(window, padding="15")
  2041.             frame.pack(fill="both", expand=True)
  2042.  
  2043.             ttk.Label(frame, text=f"Edycja instancji {version}", font=("Segoe UI", 14, "bold")).pack(pady=(0, 15))
  2044.  
  2045.             form_frame = ttk.Frame(frame)
  2046.             form_frame.pack(fill="both", expand=True)
  2047.  
  2048.             ttk.Label(form_frame, text="Nazwa użytkownika:").grid(row=0, column=0, sticky="w", pady=(0, 5))
  2049.             username_entry = ttk.Entry(form_frame)
  2050.             username_entry.insert(0, settings.get("username", username_var.get()))
  2051.             username_entry.grid(row=0, column=1, sticky="ew", pady=(0, 5), padx=(5, 0))
  2052.  
  2053.             ttk.Label(form_frame, text="Pamięć RAM (GB):").grid(row=1, column=0, sticky="w", pady=(10, 5))
  2054.             memory_spin = tk.Spinbox(form_frame, from_=1, to=16, width=5, bg=theme_colors["ENTRY_BG"], fg=theme_colors["FG_COLOR"], highlightthickness=0)
  2055.             memory_spin.delete(0, tk.END)
  2056.             memory_spin.insert(0, settings.get("memory", memory_var.get()))
  2057.             memory_spin.grid(row=1, column=1, sticky="w", pady=(10, 5), padx=(5, 0))
  2058.  
  2059.             ttk.Label(form_frame, text="Ścieżka Java:").grid(row=2, column=0, sticky="w", pady=(10, 5))
  2060.             java_var = tk.StringVar()
  2061.             java_combo = ttk.Combobox(form_frame, textvariable=java_var, state="readonly")
  2062.             java_paths = find_java()
  2063.             current_java = settings.get("java_path", instance.get("java_path", ""))
  2064.             java_values = [f"{p} ({v})" for p, v in java_paths]
  2065.            
  2066.             # Jeśli zapisana Java istnieje, dodaj ją do listy, jeśli nie ma jej w java_paths
  2067.             if current_java and not any(p == current_java for p, v in java_paths):
  2068.                 java_values.append(f"{current_java} (zapisana)")
  2069.                 java_paths.append((current_java, "zapisana"))
  2070.            
  2071.             java_combo['values'] = java_values
  2072.             java_combo.grid(row=2, column=1, sticky="ew", pady=(10, 5), padx=(5, 0))
  2073.            
  2074.             # Ustaw zapisana Javę, jeśli istnieje
  2075.             if current_java:
  2076.                 for i, (path, ver) in enumerate(java_paths):
  2077.                     if path == current_java:
  2078.                         java_combo.current(i)
  2079.                         break
  2080.                 else:
  2081.                     java_combo.current(len(java_paths) - 1)  # Wybierz "zapisana", jeśli dodana
  2082.             elif java_paths:
  2083.                 # Jeśli brak zapisanej Javy, wybierz najlepszą dla wersji
  2084.                 version_info = get_version_info(version)
  2085.                 required_java = get_required_java(version, version_info)
  2086.                 for i, (path, ver) in enumerate(java_paths):
  2087.                     if check_java_version(ver, required_java):
  2088.                         java_combo.current(i)
  2089.                         break
  2090.                 else:
  2091.                     java_combo.current(0)
  2092.  
  2093.             options_frame = ttk.Frame(form_frame)
  2094.             options_frame.grid(row=3, column=0, columnspan=2, sticky="ew", pady=(10, 5))
  2095.  
  2096.             shared_assets = tk.BooleanVar(value=settings.get("shared_assets", shared_assets_var.get()))
  2097.             shared_libs = tk.BooleanVar(value=settings.get("shared_libraries", shared_libraries_var.get()))
  2098.             shared_natives = tk.BooleanVar(value=settings.get("shared_natives", shared_natives_var.get()))
  2099.  
  2100.             tk.Checkbutton(options_frame, text="Współdziel assets", variable=shared_assets,
  2101.                            bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"], selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"],
  2102.                            font=("Segoe UI", 9)).pack(anchor="w", pady=2)
  2103.             tk.Checkbutton(options_frame, text="Współdziel biblioteki", variable=shared_libs,
  2104.                            bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"], selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"],
  2105.                            font=("Segoe UI", 9)).pack(anchor="w", pady=2)
  2106.             tk.Checkbutton(options_frame, text="Współdziel natywne biblioteki", variable=shared_natives,
  2107.                            bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"], selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"],
  2108.                            font=("Segoe UI", 9)).pack(anchor="w", pady=2)
  2109.  
  2110.             btn_frame = ttk.Frame(frame)
  2111.             btn_frame.pack(fill="x", pady=(15, 0))
  2112.  
  2113.             ttk.Button(btn_frame, text="Anuluj", command=window.destroy, width=15).pack(side="right", padx=5)
  2114.             ttk.Button(btn_frame, text="Zapisz", command=lambda: [
  2115.                 update_instance(version, {
  2116.                     "username": username_entry.get(),
  2117.                     "memory": memory_spin.get(),
  2118.                     "java_path": java_paths[int(java_combo.current())][0] if java_combo.current() != -1 else current_java,
  2119.                     "shared_assets": shared_assets.get(),
  2120.                     "shared_libraries": shared_libs.get(),
  2121.                     "shared_natives": shared_natives.get()
  2122.                 }),
  2123.                 window.destroy()
  2124.             ]).pack(side="right", padx=5)
  2125.  
  2126.         def update_instance(version, settings):
  2127.             instance = instances[version]
  2128.             instance["settings"] = settings
  2129.             info = get_version_info(version)
  2130.             if info:
  2131.                 version_dir = instance["path"]
  2132.                 bat_path = os.path.join(version_dir, "start.bat")
  2133.                 classpath = f"{version}.jar"
  2134.                 libraries_dir = LIBRARIES_DIR if settings.get("shared_libraries", True) else os.path.join(version_dir, "libraries")
  2135.                 for lib in info.get("libraries", []):
  2136.                     if "downloads" in lib and "artifact" in lib["downloads"]:
  2137.                         lib_path = lib["downloads"]["artifact"]["path"].replace("/", os.sep)
  2138.                         classpath += f";{os.path.join(libraries_dir, lib_path)}"
  2139.                 assets_path = ASSETS_DIR if settings.get("shared_assets", True) else os.path.join(version_dir, "assets")
  2140.                 natives_path = NATIVES_DIR if settings.get("shared_natives", True) else os.path.join(version_dir, "natives")
  2141.                 java_path = settings.get("java_path", instance.get("java_path", ""))
  2142.                 asset_index = info.get("assetIndex", {}).get("id", version)
  2143.                 with open(bat_path, "w", encoding="utf-8") as f:
  2144.                     if is_new_launcher(version):
  2145.                         f.write(f'@echo off\n')
  2146.                         f.write(f'title Minecraft {version}\n')
  2147.                         f.write(f'"{java_path}" -Xmx{settings["memory"]}G -Djava.library.path="{natives_path}" -Dorg.lwjgl.util.Debug=true -cp "{classpath}" net.minecraft.client.main.Main ')
  2148.                         f.write(f'--username {settings["username"]} --version {version} --gameDir . ')
  2149.                         f.write(f'--assetsDir "{assets_path}" --assetIndex {asset_index} ')
  2150.                         f.write(f'--accessToken null --uuid 0 --userType legacy\n')
  2151.                         f.write(f'pause\n')
  2152.                     else:
  2153.                         f.write(f'@echo off\n')
  2154.                         f.write(f'title Minecraft {version}\n')
  2155.                         f.write(f'"{java_path}" -Xmx{settings["memory"]}G -cp "{version}.jar" net.minecraft.LauncherFrame {settings["username"]} player_session_id\n')
  2156.                         f.write(f'pause\n')
  2157.             save_config()
  2158.             log_to_console(console, f"Instancja {version} zaktualizowana", "SUCCESS")
  2159.             refresh_instances()
  2160.  
  2161.         def show_download():
  2162.             frame = ttk.Frame(content_frame)
  2163.             frame.pack(fill="both", expand=True)
  2164.  
  2165.             ttk.Label(frame, text="Pobierz nową wersję Minecraft", font=("Segoe UI", 14, "bold")).pack(pady=(0, 15))
  2166.  
  2167.             form_frame = ttk.Frame(frame)
  2168.             form_frame.pack(fill="both", expand=True)
  2169.  
  2170.             ttk.Label(form_frame, text="Filtry wersji:").grid(row=0, column=0, sticky="w", pady=(0, 5))
  2171.             filters_frame = ttk.Frame(form_frame)
  2172.             filters_frame.grid(row=0, column=1, sticky="w", pady=(0, 5))
  2173.            
  2174.             tk.Checkbutton(filters_frame, text="Snapshoty", variable=snapshots_var, bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"],
  2175.                            selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"], font=("Segoe UI", 9),
  2176.                            command=lambda: [save_config(), refresh_version_combo(combo, version_var)]).pack(anchor="w", pady=2)
  2177.             tk.Checkbutton(filters_frame, text="Release", variable=releases_var, bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"],
  2178.                            selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"], font=("Segoe UI", 9),
  2179.                            command=lambda: [save_config(), refresh_version_combo(combo, version_var)]).pack(anchor="w", pady=2)
  2180.             tk.Checkbutton(filters_frame, text="Alpha", variable=alpha_var, bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"],
  2181.                            selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"], font=("Segoe UI", 9),
  2182.                            command=lambda: [save_config(), refresh_version_combo(combo, version_var)]).pack(anchor="w", pady=2)
  2183.             tk.Checkbutton(filters_frame, text="Beta", variable=beta_var, bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"],
  2184.                            selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"], font=("Segoe UI", 9),
  2185.                            command=lambda: [save_config(), refresh_version_combo(combo, version_var)]).pack(anchor="w", pady=2)
  2186.  
  2187.             ttk.Label(form_frame, text="Wersja Minecrafta:").grid(row=1, column=0, sticky="w", pady=(10, 5))
  2188.             version_var = tk.StringVar()
  2189.             versions = get_versions()
  2190.             combo = ttk.Combobox(form_frame, textvariable=version_var, state="readonly", values=versions)
  2191.             combo.grid(row=1, column=1, sticky="ew", pady=(10, 5), padx=(5, 0))
  2192.             if versions and pending_version in versions:
  2193.                 combo.set(pending_version)
  2194.             elif versions:
  2195.                 combo.current(0)
  2196.             else:
  2197.                 messagebox.showwarning("Uwaga", "Włącz przynajmniej jeden filtr wersji (Snapshoty, Release, Alpha, Beta)!")
  2198.  
  2199.             console_frame = ttk.Frame(frame)
  2200.             console_frame.pack(fill="both", expand=True, pady=(15, 0))
  2201.  
  2202.             global console
  2203.             console = scrolledtext.ScrolledText(
  2204.                 console_frame, height=10, wrap=tk.WORD, bg=theme_colors["CONSOLE_BG"], fg=theme_colors["CONSOLE_FG"],
  2205.                 state="disabled", font=("Consolas", 9)
  2206.             )
  2207.             console.pack(fill="both", expand=True)
  2208.  
  2209.             btn_frame = ttk.Frame(frame)
  2210.             btn_frame.pack(fill="x", pady=(15, 0))
  2211.  
  2212.             ttk.Button(btn_frame, text="Pobierz", command=lambda: start_download({
  2213.                 "username": pending_instance_settings.get("username", username_var.get()),
  2214.                 "memory": pending_instance_settings.get("memory", memory_var.get()),
  2215.                 "java_path": pending_instance_settings.get("java_path", ""),
  2216.                 "shared_assets": pending_instance_settings.get("shared_assets", shared_assets_var.get()),
  2217.                 "shared_libraries": pending_instance_settings.get("shared_libraries", shared_libraries_var.get()),
  2218.                 "shared_natives": pending_instance_settings.get("shared_natives", shared_natives_var.get())
  2219.             }, version_var.get()) if version_var.get() else messagebox.showwarning("Uwaga", "Wybierz wersję!"), width=15).pack()
  2220.  
  2221.         def start_download(settings, version):
  2222.             global download_thread, download_active
  2223.             if download_active:
  2224.                 messagebox.showwarning("Uwaga", "Pobieranie już w toku!")
  2225.                 return
  2226.             if not version:
  2227.                 messagebox.showwarning("Uwaga", "Wybierz wersję!")
  2228.                 return
  2229.             download_active = True
  2230.             download_thread = threading.Thread(
  2231.                 target=download_version,
  2232.                 args=(version, settings, console),
  2233.                 daemon=True
  2234.             )
  2235.             download_thread.start()
  2236.  
  2237.         def show_mod_loader_tab():
  2238.             frame = ttk.Frame(content_frame)
  2239.             frame.pack(fill="both", expand=True)
  2240.  
  2241.             ttk.Label(frame, text="Instalacja mod loaderów", font=("Segoe UI", 14, "bold")).pack(pady=(0, 15))
  2242.  
  2243.             form_frame = ttk.Frame(frame)
  2244.             form_frame.pack(fill="both", expand=True)
  2245.  
  2246.             ttk.Label(form_frame, text="Wybierz instancję:").grid(row=0, column=0, sticky="w", pady=(0, 5))
  2247.             instance_var = tk.StringVar()
  2248.             instances_combo = ttk.Combobox(form_frame, textvariable=instance_var, state="readonly", values=list(instances.keys()))
  2249.             instances_combo.grid(row=0, column=1, sticky="ew", pady=(0, 5), padx=(5, 0))
  2250.             if instances:
  2251.                 instances_combo.current(0)
  2252.  
  2253.             btn_frame = ttk.Frame(form_frame)
  2254.             btn_frame.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(10, 0))
  2255.  
  2256.             ttk.Button(btn_frame, text="Zainstaluj loader", command=lambda: show_mod_loader_installer(instance_var.get()) if instance_var.get() else messagebox.showwarning("Uwaga", "Wybierz instancję!")).pack(side="left", padx=5)
  2257.             ttk.Button(btn_frame, text="Pokaż statystyki", command=lambda: show_instance_stats(instance_var.get()) if instance_var.get() else messagebox.showwarning("Uwaga", "Wybierz instancję!")).pack(side="left", padx=5)
  2258.             ttk.Button(btn_frame, text="Dodaj notatkę", command=lambda: add_instance_note(instance_var.get()) if instance_var.get() else messagebox.showwarning("Uwaga", "Wybierz instancję!")).pack(side="left", padx=5)
  2259.  
  2260.             info_frame = ttk.Frame(frame)
  2261.             info_frame.pack(fill="both", expand=True, pady=(15, 0))
  2262.  
  2263.             ttk.Label(info_frame, text="Obecny mod loader:").pack(anchor="w")
  2264.             loader_label = ttk.Label(info_frame, text="", font=("Segoe UI", 10, "bold"))
  2265.             loader_label.pack(anchor="w")
  2266.  
  2267.             ttk.Label(info_frame, text="Zainstalowane mody:").pack(anchor="w", pady=(10, 0))
  2268.             mods_tree = ttk.Treeview(info_frame, columns=("name", "version"), show="headings", height=5)
  2269.             mods_tree.heading("name", text="Nazwa")
  2270.             mods_tree.heading("version", text="Wersja")
  2271.             mods_tree.column("name", width=200)
  2272.             mods_tree.column("version", width=100)
  2273.             mods_tree.pack(fill="both", expand=True)
  2274.  
  2275.             def update_info():
  2276.                 version = instance_var.get()
  2277.                 if not version or version not in instances:
  2278.                     return
  2279.                
  2280.                 instance = instances[version]
  2281.                 if instance.get("mod_loader"):
  2282.                     loader_label.config(text=f"{instance['mod_loader']['type'].capitalize()} {instance['mod_loader']['version']}")
  2283.                 else:
  2284.                     loader_label.config(text="Brak")
  2285.                
  2286.                 mods_tree.delete(*mods_tree.get_children())
  2287.                 for mod in instance.get("mods", []):
  2288.                     mods_tree.insert("", "end", values=(mod["name"], mod.get("version", "")))
  2289.  
  2290.             instances_combo.bind("<<ComboboxSelected>>", lambda e: update_info())
  2291.             update_info()
  2292.  
  2293.         def show_settings():
  2294.             frame = ttk.Frame(content_frame)
  2295.             frame.pack(fill="both", expand=True)
  2296.  
  2297.             ttk.Label(frame, text="Ustawienia Launchera", font=("Segoe UI", 14, "bold")).pack(pady=(0, 15))
  2298.  
  2299.             form_frame = ttk.Frame(frame)
  2300.             form_frame.pack(fill="both", expand=True)
  2301.  
  2302.             ttk.Label(form_frame, text="Domyślna nazwa użytkownika:").grid(row=0, column=0, sticky="w", pady=(0, 5))
  2303.             ttk.Entry(form_frame, textvariable=username_var).grid(row=0, column=1, sticky="ew", pady=(0, 5), padx=(5, 0))
  2304.  
  2305.             ttk.Label(form_frame, text="Domyślna pamięć RAM (GB):").grid(row=1, column=0, sticky="w", pady=(10, 5))
  2306.             memory_spin = tk.Spinbox(form_frame, from_=1, to=16, textvariable=memory_var, width=5,
  2307.                                      bg=theme_colors["ENTRY_BG"], fg=theme_colors["FG_COLOR"], highlightthickness=0)
  2308.             memory_spin.grid(row=1, column=1, sticky="w", pady=(10, 5), padx=(5, 0))
  2309.  
  2310.             options_frame = ttk.Frame(form_frame)
  2311.             options_frame.grid(row=2, column=0, columnspan=2, sticky="ew", pady=(10, 5))
  2312.  
  2313.             tk.Checkbutton(options_frame, text="Współdziel assets między instancjami", variable=shared_assets_var,
  2314.                            bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"], selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"],
  2315.                            font=("Segoe UI", 9)).pack(anchor="w", pady=2)
  2316.             tk.Checkbutton(options_frame, text="Współdziel biblioteki między instancjami", variable=shared_libraries_var,
  2317.                            bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"], selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"],
  2318.                            font=("Segoe UI", 9)).pack(anchor="w", pady=2)
  2319.             tk.Checkbutton(options_frame, text="Współdziel natywne biblioteki między instancjami", variable=shared_natives_var,
  2320.                            bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"], selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"],
  2321.                            font=("Segoe UI", 9)).pack(anchor="w", pady=2)
  2322.  
  2323.             ttk.Label(form_frame, text="Domyślne filtry wersji:").grid(row=3, column=0, sticky="w", pady=(10, 5))
  2324.             filters_frame = ttk.Frame(form_frame)
  2325.             filters_frame.grid(row=3, column=1, sticky="w", pady=(10, 5))
  2326.  
  2327.             tk.Checkbutton(filters_frame, text="Snapshoty", variable=snapshots_var, bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"],
  2328.                            selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"], font=("Segoe UI", 9),
  2329.                            command=save_config).pack(anchor="w", pady=2)
  2330.             tk.Checkbutton(filters_frame, text="Release", variable=releases_var, bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"],
  2331.                            selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"], font=("Segoe UI", 9),
  2332.                            command=save_config).pack(anchor="w", pady=2)
  2333.             tk.Checkbutton(filters_frame, text="Alpha", variable=alpha_var, bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"],
  2334.                            selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"], font=("Segoe UI", 9),
  2335.                            command=save_config).pack(anchor="w", pady=2)
  2336.             tk.Checkbutton(filters_frame, text="Beta", variable=beta_var, bg=theme_colors["BG_COLOR"], fg=theme_colors["FG_COLOR"],
  2337.                            selectcolor=theme_colors["BG_COLOR"], activebackground=theme_colors["BG_COLOR"], font=("Segoe UI", 9),
  2338.                            command=save_config).pack(anchor="w", pady=2)
  2339.  
  2340.             theme_frame = ttk.Frame(form_frame)
  2341.             theme_frame.grid(row=4, column=0, columnspan=2, sticky="ew", pady=(10, 5))
  2342.  
  2343.             ttk.Label(theme_frame, text="Motyw:").pack(side="left", padx=5)
  2344.             ttk.Button(theme_frame, text="Ciemny/Jasny", command=toggle_theme).pack(side="left", padx=5)
  2345.  
  2346.             btn_frame = ttk.Frame(frame)
  2347.             btn_frame.pack(fill="x", pady=(15, 0))
  2348.  
  2349.             ttk.Button(btn_frame, text="Zapisz ustawienia", command=save_config, width=20).pack()
  2350.  
  2351.         def show_console():
  2352.             frame = ttk.Frame(content_frame)
  2353.             frame.pack(fill="both", expand=True)
  2354.  
  2355.             ttk.Label(frame, text="Konsola Launchera", font=("Segoe UI", 14, "bold")).pack(pady=(0, 15))
  2356.  
  2357.             console_frame = ttk.Frame(frame)
  2358.             console_frame.pack(fill="both", expand=True)
  2359.  
  2360.             global console
  2361.             console = scrolledtext.ScrolledText(
  2362.                 console_frame, height=20, wrap=tk.WORD, bg=theme_colors["CONSOLE_BG"], fg=theme_colors["CONSOLE_FG"],
  2363.                 state="disabled", font=("Consolas", 9)
  2364.             )
  2365.             console.pack(fill="both", expand=True)
  2366.  
  2367.             btn_frame = ttk.Frame(frame)
  2368.             btn_frame.pack(fill="x", pady=(10, 0))
  2369.  
  2370.             ttk.Button(btn_frame, text="Kopiuj", command=lambda: copy_console(console), width=15).pack(side="left", padx=5)
  2371.             ttk.Button(btn_frame, text="Wyczyść", command=lambda: clear_console(console), width=15).pack(side="left", padx=5)
  2372.  
  2373.         def refresh_version_combo(combo, version_var):
  2374.             versions = get_versions()
  2375.             combo['values'] = versions
  2376.             if versions:
  2377.                 version_var.set(versions[0])
  2378.             else:
  2379.                 version_var.set("")
  2380.                 messagebox.showwarning("Uwaga", "Włącz przynajmniej jeden filtr wersji (Snapshoty, Release, Alpha, Beta)!")
  2381.  
  2382.         print("Inicjalizacja zakończona, uruchamiam UI...")
  2383.         switch_tab("Instancje")
  2384.         root.mainloop()
  2385.     except Exception as e:
  2386.         print(f"CRASH: {e}")
  2387.         with open(os.path.join(LOGS_DIR, "crash.log"), "a", encoding="utf-8") as f:
  2388.             f.write(f"[{datetime.now()}] CRASH: {e}\n")
  2389.         raise
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement