Advertisement
PaffcioStudio

Untitled

Apr 30th, 2025
172
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 55.57 KB | None | 0 0
  1. from PyQt6.QtWidgets import (
  2.     QApplication, QMainWindow, QToolBar, QWidget, QLabel,
  3.     QPushButton, QHBoxLayout, QVBoxLayout, QScrollArea,
  4.     QGridLayout, QMenu, QDialog, QFormLayout, QLineEdit,
  5.     QComboBox, QCheckBox, QMessageBox, QGroupBox, QRadioButton,
  6.     QDialogButtonBox, QSizePolicy, QProgressBar, QFileDialog
  7. )
  8. from PyQt6.QtGui import QIcon, QAction, QDrag, QDesktopServices
  9. from PyQt6.QtCore import Qt, QMimeData, QUrl, QThread, pyqtSignal, QProcess
  10. import sys
  11. import os
  12. import json
  13. import logging
  14. import configparser
  15. from datetime import datetime
  16. import requests
  17. import shutil
  18. import platform
  19. import subprocess
  20. from pathlib import Path
  21.  
  22. # Konfiguracja logowania
  23. def setup_logging():
  24.     log_dir = "minecraft/logs"
  25.     os.makedirs(log_dir, exist_ok=True)
  26.    
  27.     logging.basicConfig(
  28.         filename=f"{log_dir}/launcher.log",
  29.         level=logging.INFO,
  30.         format="%(asctime)s - %(levelname)s - %(message)s"
  31.     )
  32.     for log_file in ["error.log", "download.log"]:
  33.         with open(f"{log_dir}/{log_file}", "a") as f:
  34.             pass
  35.  
  36. # Tworzenie struktury folderów
  37. def setup_directories():
  38.     directories = [
  39.         "minecraft/assets/indexes",
  40.         "minecraft/assets/objects",
  41.         "minecraft/libraries",
  42.         "minecraft/instances",
  43.         "minecraft/javas",
  44.         "minecraft/versions"
  45.     ]
  46.     for directory in directories:
  47.         os.makedirs(directory, exist_ok=True)
  48.  
  49. # Zapisywanie stanu pobierania
  50. def save_download_state(download_type, file_path, file_url):
  51.     state_file = "minecraft/downloads.json"
  52.     state = {}
  53.     try:
  54.         with open(state_file, "r") as f:
  55.             state = json.load(f)
  56.     except FileNotFoundError:
  57.         pass
  58.    
  59.     if download_type not in state:
  60.         state[download_type] = {}
  61.     state[download_type][file_path] = file_url
  62.    
  63.     with open(state_file, "w") as f:
  64.         json.dump(state, f, indent=4)
  65.  
  66. # Wczytywanie stanu pobierania
  67. def load_download_state():
  68.     state_file = "minecraft/downloads.json"
  69.     try:
  70.         with open(state_file, "r") as f:
  71.             return json.load(f)
  72.     except FileNotFoundError:
  73.         return {}
  74.  
  75. # Klasa do pobierania plików w tle
  76. class DownloadWorker(QThread):
  77.     progress_updated = pyqtSignal(str, int, int, int, str, float, float)  # typ, procent, pobrane, całkowite, nazwa pliku, MB pobrane, MB całkowite
  78.     download_finished = pyqtSignal(bool, str)  # sukces, wiadomość
  79.     file_downloaded = pyqtSignal(str, str)  # typ, ścieżka pliku
  80.  
  81.     def __init__(self, files_to_download, download_type, parent=None):
  82.         super().__init__(parent)
  83.         self.files_to_download = files_to_download
  84.         self.download_type = download_type
  85.         self.canceled = False
  86.  
  87.     def run(self):
  88.         try:
  89.             total_files = len(self.files_to_download)
  90.             completed_files = 0
  91.             for file_path, file_url in self.files_to_download.items():
  92.                 if self.canceled:
  93.                     break
  94.  
  95.                 if os.path.exists(file_path):
  96.                     completed_files += 1
  97.                     self.file_downloaded.emit(self.download_type, file_path)
  98.                     self.progress_updated.emit(self.download_type, int((completed_files / total_files) * 100), completed_files, total_files, os.path.basename(file_path), 0, 0)
  99.                     continue
  100.  
  101.                 os.makedirs(os.path.dirname(file_path), exist_ok=True)
  102.                 with requests.get(file_url, stream=True) as r:
  103.                     r.raise_for_status()
  104.                     total_size = int(r.headers.get('content-length', 0))
  105.                     total_mb = total_size / (1024 * 1024)
  106.                     downloaded = 0
  107.                     with open(file_path, 'wb') as f:
  108.                         for chunk in r.iter_content(chunk_size=8192):
  109.                             if self.canceled:
  110.                                 break
  111.                             f.write(chunk)
  112.                             downloaded += len(chunk)
  113.                             downloaded_mb = downloaded / (1024 * 1024)
  114.                             self.progress_updated.emit(
  115.                                 self.download_type,
  116.                                 int((completed_files / total_files) * 100),
  117.                                 completed_files,
  118.                                 total_files,
  119.                                 os.path.basename(file_path),
  120.                                 round(downloaded_mb, 2),
  121.                                 round(total_mb, 2)
  122.                             )
  123.                     if not self.canceled:
  124.                         save_download_state(self.download_type, file_path, file_url)
  125.                         completed_files += 1
  126.                         self.file_downloaded.emit(self.download_type, file_path)
  127.                         self.progress_updated.emit(
  128.                             self.download_type,
  129.                             int((completed_files / total_files) * 100),
  130.                             completed_files,
  131.                             total_files,
  132.                             os.path.basename(file_path),
  133.                             round(downloaded_mb, 2),
  134.                             round(total_mb, 2)
  135.                         )
  136.  
  137.             if not self.canceled:
  138.                 self.download_finished.emit(True, f"Pobieranie {self.download_type} zakończone!")
  139.             else:
  140.                 self.download_finished.emit(False, f"Pobieranie {self.download_type} przerwane")
  141.         except Exception as e:
  142.             logging.error(f"Błąd podczas pobierania {self.download_type}: {str(e)}")
  143.             self.download_finished.emit(False, f"Błąd: {str(e)}")
  144.  
  145.     def cancel(self):
  146.         self.canceled = True
  147.  
  148. # Klasa do uruchamiania Minecrafta
  149. class MinecraftRunner(QThread):
  150.     output_received = pyqtSignal(str)
  151.     finished = pyqtSignal(int)
  152.  
  153.     def __init__(self, command, working_dir, parent=None):
  154.         super().__init__(parent)
  155.         self.command = command
  156.         self.working_dir = working_dir
  157.         self.process = None
  158.  
  159.     def run(self):
  160.         try:
  161.             self.process = QProcess()
  162.             self.process.setWorkingDirectory(self.working_dir)
  163.             self.process.readyReadStandardOutput.connect(self.handle_output)
  164.             self.process.readyReadStandardError.connect(self.handle_error)
  165.             self.process.finished.connect(self.on_finished)
  166.             self.process.start(self.command[0], self.command[1:])
  167.             self.process.waitForFinished(-1)
  168.         except Exception as e:
  169.             self.output_received.emit(f"Błąd podczas uruchamiania: {str(e)}")
  170.             self.finished.emit(1)
  171.  
  172.     def handle_output(self):
  173.         output = self.process.readAllStandardOutput().data().decode().strip()
  174.         if output:
  175.             self.output_received.emit(output)
  176.  
  177.     def handle_error(self):
  178.         error = self.process.readAllStandardError().data().decode().strip()
  179.         if error:
  180.             self.output_received.emit(error)
  181.  
  182.     def on_finished(self, exit_code):
  183.         self.finished.emit(exit_code)
  184.  
  185.     def stop(self):
  186.         if self.process:
  187.             self.process.terminate()
  188.  
  189. # Wyszukiwanie Java
  190. class JavaFinder:
  191.     @staticmethod
  192.     def find_java_for_version(version_info):
  193.         required_java = version_info.get("javaVersion", {}).get("majorVersion", 8)
  194.         logging.info(f"Szukam Java w wersji {required_java}")
  195.         javas_dir = "minecraft/javas"
  196.         if os.path.exists(javas_dir):
  197.             for root, dirs, _ in os.walk(javas_dir):
  198.                 for dir_name in dirs:
  199.                     if f"java-{required_java}" in dir_name.lower():
  200.                         java_path = os.path.join(root, dir_name, "bin", "java.exe" if platform.system() == "Windows" else "java")
  201.                         if os.path.exists(java_path):
  202.                             return java_path
  203.         java_home = os.environ.get("JAVA_HOME")
  204.         if java_home:
  205.             java_path = os.path.join(java_home, "bin", "java.exe" if platform.system() == "Windows" else "java")
  206.             if os.path.exists(java_path):
  207.                 return java_path
  208.         try:
  209.             java_path = shutil.which("java")
  210.             if java_path:
  211.                 version_output = subprocess.check_output([java_path, "-version"], stderr=subprocess.STDOUT).decode()
  212.                 if f'version "{required_java}' in version_output or f'version "1.{required_java}' in version_output:
  213.                     return java_path
  214.         except:
  215.             pass
  216.         return None
  217.  
  218. # Klasa kafelka instancji
  219. class InstanceTile(QWidget):
  220.     def __init__(self, name, version, group=None, parent=None):
  221.         super().__init__()
  222.         self.name = name
  223.         self.version = version
  224.         self.group = group
  225.         self.parent = parent
  226.         self.is_selected = False
  227.         self.is_running = False
  228.         self.setFixedSize(120, 120)
  229.  
  230.         layout = QVBoxLayout()
  231.         layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
  232.         layout.setSpacing(8)
  233.  
  234.         self.icon_container = QWidget()
  235.         icon_layout = QVBoxLayout()
  236.         icon_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
  237.         self.icon = QLabel("🟩")
  238.         self.icon.setAlignment(Qt.AlignmentFlag.AlignCenter)
  239.         self.icon.setStyleSheet("font-size: 48px; border: none;")
  240.         self.status_icon = QLabel("")
  241.         self.status_icon.setAlignment(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignRight)
  242.         self.status_icon.setStyleSheet("font-size: 16px; position: absolute; margin-bottom: -10px; margin-right: -10px;")
  243.         icon_layout.addWidget(self.icon)
  244.         icon_layout.addWidget(self.status_icon)
  245.         self.icon_container.setLayout(icon_layout)
  246.  
  247.         self.version_label = QLabel(version)
  248.         self.version_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
  249.         self.version_label.setStyleSheet("font-size: 10px; color: gray;")
  250.  
  251.         self.name_label = QLabel(name)
  252.         self.name_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
  253.         self.name_label.setStyleSheet("font-size: 12px;")
  254.  
  255.         layout.addWidget(self.version_label)
  256.         layout.addWidget(self.icon_container)
  257.         layout.addWidget(self.name_label)
  258.         self.setLayout(layout)
  259.  
  260.         self.update_style()
  261.         self.setMouseTracking(True)
  262.  
  263.     def mousePressEvent(self, event):
  264.         if event.button() == Qt.MouseButton.LeftButton:
  265.             if self.parent.selected_tile:
  266.                 self.parent.selected_tile.is_selected = False
  267.                 self.parent.selected_tile.update_style()
  268.             self.is_selected = True
  269.             self.parent.selected_tile = self
  270.             self.update_style()
  271.             self.parent.update_right_panel()
  272.             logging.info(f"Zaznaczono instancję: {self.name}")
  273.             print(f"[INFO] Zaznaczono instancję: {self.name}")
  274.  
  275.             drag = QDrag(self)
  276.             mime = QMimeData()
  277.             mime.setText(self.name)
  278.             drag.setMimeData(mime)
  279.             drag.exec(Qt.DropAction.MoveAction)
  280.  
  281.     def mouseDoubleClickEvent(self, event):
  282.         if event.button() == Qt.MouseButton.LeftButton:
  283.             self.parent.launch_selected()
  284.  
  285.     def keyPressEvent(self, event):
  286.         if event.key() == Qt.Key.Key_F2:
  287.             self.edit_name()
  288.  
  289.     def update_style(self):
  290.         if self.is_selected:
  291.             self.setStyleSheet("background-color: #d3e3fd; border: none; border-radius: 5px;")
  292.         else:
  293.             self.setStyleSheet("background-color: white; border: none; border-radius: 5px;")
  294.  
  295.     def contextMenuEvent(self, event):
  296.         menu = QMenu(self)
  297.         run_action = menu.addAction(QIcon.fromTheme("media-playback-start"), "Uruchom")
  298.         kill_action = menu.addAction(QIcon.fromTheme("process-stop"), "Zakończ")
  299.         edit_action = menu.addAction(QIcon.fromTheme("document-edit"), "Edytuj")
  300.         rename_action = menu.addAction(QIcon.fromTheme("edit-rename"), "Zmień nazwę")
  301.         change_group_action = menu.addAction(QIcon.fromTheme("list"), "Zmień grupę")
  302.         folder_action = menu.addAction(QIcon.fromTheme("folder"), "Folder")
  303.         export_menu = menu.addMenu("Eksportuj")
  304.         export_menu.addAction("Eksportuj ZIP")
  305.         export_menu.addAction("Eksportuj JSON")
  306.         copy_action = menu.addAction(QIcon.fromTheme("edit-copy"), "Kopiuj")
  307.         delete_action = menu.addAction(QIcon.fromTheme("edit-delete"), "Usuń")
  308.         shortcut_action = menu.addAction(QIcon.fromTheme("link"), "Utwórz skrót")
  309.  
  310.         action = menu.exec(event.globalPos())
  311.  
  312.         if action == run_action:
  313.             self.parent.launch_selected()
  314.         elif action == kill_action:
  315.             self.parent.kill_selected()
  316.         elif action == edit_action:
  317.             logging.info(f"Edytowanie instancji: {self.name}")
  318.             print(f"[INFO] Edytowanie instancji: {self.name}")
  319.         elif action == rename_action:
  320.             self.edit_name()
  321.         elif action == change_group_action:
  322.             logging.info(f"Zmiana grupy dla: {self.name}")
  323.             print(f"[INFO] Zmiana grupy dla: {self.name}")
  324.         elif action == folder_action:
  325.             instance_dir = f"minecraft/instances/{self.name}"
  326.             if os.path.exists(instance_dir):
  327.                 QDesktopServices.openUrl(QUrl.fromLocalFile(instance_dir))
  328.             logging.info(f"Otwieranie folderu instancji: {self.name}")
  329.             print(f"[INFO] Otwieranie folderu instancji: {self.name}")
  330.  
  331.     def edit_name(self):
  332.         dialog = QDialog(self)
  333.         dialog.setWindowTitle("Zmień nazwę")
  334.         layout = QFormLayout()
  335.         new_name = QLineEdit(self.name)
  336.         layout.addRow("Nowa nazwa:", new_name)
  337.         button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
  338.         button_box.accepted.connect(dialog.accept)
  339.         button_box.rejected.connect(dialog.reject)
  340.         layout.addWidget(button_box)
  341.         dialog.setLayout(layout)
  342.  
  343.         if dialog.exec() == QDialog.DialogCode.Accepted:
  344.             old_name = self.name
  345.             self.name = new_name.text()
  346.             self.name_label.setText(self.name)
  347.             self.parent.update_instance_name(old_name, self.name, self.group)
  348.             logging.info(f"Zmieniono nazwę instancji na: {self.name}")
  349.             print(f"[INFO] Zmieniono nazwę instancji na: {self.name}")
  350.  
  351. # Sekcja grupy instancji
  352. class GroupSection(QWidget):
  353.     def __init__(self, group_name, tiles, parent=None):
  354.         super().__init__()
  355.         self.group_name = group_name
  356.         self.tiles = tiles
  357.         self.parent = parent
  358.         self.setAcceptDrops(True)
  359.         self.setStyleSheet("background-color: white;")
  360.  
  361.         layout = QVBoxLayout()
  362.         self.grid_widget = QWidget()
  363.         self.grid_layout = QGridLayout()
  364.         self.grid_layout.setSpacing(2)
  365.  
  366.         for i, tile in enumerate(self.tiles):
  367.             self.grid_layout.addWidget(tile, i // 4, i % 4)
  368.  
  369.         self.grid_widget.setLayout(self.grid_layout)
  370.         layout.addWidget(self.grid_widget)
  371.         self.setLayout(layout)
  372.  
  373.     def dragEnterEvent(self, event):
  374.         if event.mimeData().hasText():
  375.             self.setStyleSheet("background-color: lightgray;")
  376.             event.acceptProposedAction()
  377.  
  378.     def dragLeaveEvent(self, event):
  379.         self.setStyleSheet("background-color: white;")
  380.  
  381.     def dropEvent(self, event):
  382.         source_name = event.mimeData().text()
  383.         source_tile = None
  384.         for group, tiles in self.parent.groups.items():
  385.             for tile in tiles:
  386.                 if tile.name == source_name:
  387.                     source_tile = tile
  388.                     break
  389.             if source_tile:
  390.                 break
  391.  
  392.         if source_tile and source_tile.group != self.group_name:
  393.             old_group = source_tile.group
  394.             self.parent.groups[old_group].remove(source_tile)
  395.             source_tile.group = self.group_name
  396.             self.parent.groups[self.group_name].append(source_tile)
  397.             self.parent.update_instance_group(source_name, old_group, self.group_name)
  398.             self.parent.refresh_instances()
  399.             logging.info(f"Przeniesiono instancję {source_name} do grupy {self.group_name}")
  400.             print(f"[INFO] Przeniesiono instancję {source_name} do grupy {self.group_name}")
  401.         self.setStyleSheet("background-color: white;")
  402.  
  403. # Dialog dodawania instancji
  404. class AddInstanceDialog(QDialog):
  405.     def __init__(self, groups, versions_data, parent=None):
  406.         super().__init__(parent)
  407.         self.setWindowTitle("Dodaj Instancję")
  408.         self.setGeometry(200, 200, 500, 400)
  409.         self.versions_data = versions_data
  410.         self.parent = parent
  411.  
  412.         form_layout = QFormLayout()
  413.  
  414.         self.instance_name = QLineEdit()
  415.         form_layout.addRow("Nazwa instancji:", self.instance_name)
  416.  
  417.         self.version_combo = QComboBox()
  418.         for version in self.versions_data["versions"]:
  419.             self.version_combo.addItem(version["id"], version["id"])
  420.         form_layout.addRow("Wersja Minecraft:", self.version_combo)
  421.  
  422.         self.instance_type = QComboBox()
  423.         self.instance_type.addItems(["Vanilla", "Forge", "Fabric"])
  424.         form_layout.addRow("Typ instancji:", self.instance_type)
  425.  
  426.         self.group_combo = QComboBox()
  427.         self.group_combo.addItems(groups)
  428.         form_layout.addRow("Grupa:", self.group_combo)
  429.  
  430.         self.mod_loader_checkbox = QCheckBox("Zainstaluj mod loadera")
  431.         form_layout.addRow(self.mod_loader_checkbox)
  432.  
  433.         self.group_box = QGroupBox("Zarządzanie modami")
  434.         group_layout = QVBoxLayout()
  435.         self.enable_mods_radio = QRadioButton("Włącz mody")
  436.         self.disable_mods_radio = QRadioButton("Wyłącz mody")
  437.         self.disable_mods_radio.setChecked(True)
  438.         group_layout.addWidget(self.enable_mods_radio)
  439.         group_layout.addWidget(self.disable_mods_radio)
  440.         self.group_box.setLayout(group_layout)
  441.         form_layout.addRow(self.group_box)
  442.  
  443.         button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel)
  444.         button_box.accepted.connect(self.accept)
  445.         button_box.rejected.connect(self.reject)
  446.         form_layout.addWidget(button_box)
  447.  
  448.         self.setLayout(form_layout)
  449.  
  450.     def accept(self):
  451.         instance_name = self.instance_name.text()
  452.         if not instance_name:
  453.             QMessageBox.warning(self, "Błąd", "Nazwa instancji nie może być pusta!")
  454.             return
  455.  
  456.         version_id = self.version_combo.currentData()
  457.         version_info = next((v for v in self.versions_data["versions"] if v["id"] == version_id), None)
  458.        
  459.         if not version_info:
  460.             QMessageBox.warning(self, "Błąd", "Nie można znaleźć informacji o wybranej wersji!")
  461.             return
  462.  
  463.         super().accept()
  464.  
  465. # Dialog dodawania grupy
  466. class AddGroupDialog(QDialog):
  467.     def __init__(self):
  468.         super().__init__()
  469.         self.setWindowTitle("Dodaj Grupę")
  470.         self.setGeometry(200, 200, 300, 150)
  471.  
  472.         form_layout = QFormLayout()
  473.  
  474.         self.group_name = QLineEdit()
  475.         form_layout.addRow("Nazwa grupy:", self.group_name)
  476.  
  477.         button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel)
  478.         button_box.accepted.connect(self.accept)
  479.         button_box.rejected.connect(self.reject)
  480.         form_layout.addWidget(button_box)
  481.  
  482.         self.setLayout(form_layout)
  483.  
  484. # Dialog ustawień
  485. class SettingsDialog(QDialog):
  486.     def __init__(self, parent=None):
  487.         super().__init__(parent)
  488.         self.setWindowTitle("Ustawienia")
  489.         self.setGeometry(250, 250, 500, 400)
  490.  
  491.         form_layout = QFormLayout()
  492.  
  493.         self.username_edit = QLineEdit()
  494.         self.username_edit.setText(parent.username or "Brak użytkownika")
  495.         form_layout.addRow("Nazwa użytkownika:", self.username_edit)
  496.  
  497.         self.theme_combo = QComboBox()
  498.         self.theme_combo.addItems(["Jasny", "Ciemny"])
  499.         self.theme_combo.setCurrentText(parent.theme)
  500.         form_layout.addRow("Motyw:", self.theme_combo)
  501.  
  502.         java_group = QGroupBox("Ustawienia Java")
  503.         java_layout = QFormLayout()
  504.         self.java_path_edit = QLineEdit()
  505.         self.java_path_edit.setText(parent.settings.get("java_path", ""))
  506.         java_layout.addRow("Ścieżka do Java:", self.java_path_edit)
  507.         browse_btn = QPushButton("Przeglądaj...")
  508.         browse_btn.clicked.connect(self.browse_java_path)
  509.         java_layout.addRow(browse_btn)
  510.         self.auto_java_check = QCheckBox("Automatycznie wykrywaj Java")
  511.         self.auto_java_check.setChecked(parent.settings.get("auto_java", True))
  512.         java_layout.addRow(self.auto_java_check)
  513.         java_group.setLayout(java_layout)
  514.         form_layout.addWidget(java_group)
  515.  
  516.         button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel)
  517.         button_box.accepted.connect(self.accept)
  518.         button_box.rejected.connect(self.reject)
  519.         form_layout.addWidget(button_box)
  520.  
  521.         self.setLayout(form_layout)
  522.  
  523.     def browse_java_path(self):
  524.         file_path, _ = QFileDialog.getOpenFileName(self, "Wybierz plik Java", "", "Java (java java.exe)")
  525.         if file_path:
  526.             self.java_path_edit.setText(file_path)
  527.  
  528. # Główna klasa launchera
  529. class MCLauncher(QMainWindow):
  530.     def __init__(self):
  531.         super().__init__()
  532.         self.setWindowTitle("MCLauncher")
  533.         self.setGeometry(100, 100, 1000, 700)
  534.         self.username = None
  535.         self.theme = "Jasny"
  536.         self.groups = {}
  537.         self.group_widgets = {}
  538.         self.selected_tile = None
  539.         self.collapsed_groups = set()
  540.         self.settings = {}
  541.         self.download_worker = None
  542.         self.minecraft_runner = None
  543.         self.versions_data = None
  544.         self.current_downloads = {}
  545.         self.download_counts = {
  546.             'assets': {'completed': 0, 'total': 0},
  547.             'libraries': {'completed': 0, 'total': 0},
  548.             'natives': {'completed': 0, 'total': 0}
  549.         }
  550.  
  551.         setup_directories()
  552.         setup_logging()
  553.         self.load_settings()
  554.         self.fetch_minecraft_versions()
  555.         self.load_instgroups()
  556.         self.apply_theme()
  557.         self.create_toolbar()
  558.         self.create_main_area()
  559.         self.create_status_bar()
  560.  
  561.     def fetch_minecraft_versions(self):
  562.         try:
  563.             response = requests.get("https://launchermeta.mojang.com/mc/game/version_manifest.json")
  564.             response.raise_for_status()
  565.             self.versions_data = response.json()
  566.             with open("minecraft/versions/version_manifest.json", "w") as f:
  567.                 json.dump(self.versions_data, f)
  568.             logging.info("Pobrano listę wersji Minecraft")
  569.         except Exception as e:
  570.             logging.error(f"Błąd podczas pobierania wersji Minecraft: {str(e)}")
  571.             try:
  572.                 with open("minecraft/versions/version_manifest.json", "r") as f:
  573.                     self.versions_data = json.load(f)
  574.                 logging.info("Wczytano lokalną listę wersji Minecraft")
  575.             except:
  576.                 self.versions_data = {"versions": [{"id": "1.21.4", "url": ""}]}
  577.                 logging.warning("Użyto domyślnej wersji Minecraft")
  578.  
  579.     def load_instgroups(self):
  580.         try:
  581.             with open("minecraft/instances/instgroups.json", "r") as f:
  582.                 data = json.load(f)
  583.                 if data["formatVersion"] != "1":
  584.                     raise ValueError("Nieobsługiwana wersja formatu instgroups.json")
  585.                 self.groups = {}
  586.                 for group_name, group_data in data["groups"].items():
  587.                     self.groups[group_name] = []
  588.                     if not group_data["hidden"]:
  589.                         for instance_name in group_data["instances"]:
  590.                             instance_dir = f"minecraft/instances/{instance_name}"
  591.                             version = "1.21.4"
  592.                             if os.path.exists(f"{instance_dir}/instance.cfg"):
  593.                                 config = configparser.ConfigParser()
  594.                                 config.read(f"{instance_dir}/instance.cfg")
  595.                                 if "General" in config and "InstanceType" in config["General"]:
  596.                                     version = config["General"].get("InstanceType", "1.21.4")
  597.                             self.groups[group_name].append(InstanceTile(instance_name, version, group_name, self))
  598.         except FileNotFoundError:
  599.             self.groups = {"Modpack": [], "Vanilla-like": []}
  600.             self.save_instgroups()
  601.  
  602.     def save_instgroups(self):
  603.         data = {"formatVersion": "1", "groups": {}}
  604.         for group_name, tiles in self.groups.items():
  605.             data["groups"][group_name] = {
  606.                 "hidden": group_name in self.collapsed_groups,
  607.                 "instances": [tile.name for tile in tiles]
  608.             }
  609.         with open("minecraft/instances/instgroups.json", "w") as f:
  610.             json.dump(data, f, indent=4)
  611.  
  612.     def update_instance_name(self, old_name, new_name, group_name):
  613.         if os.path.exists(f"minecraft/instances/{old_name}"):
  614.             os.rename(f"minecraft/instances/{old_name}", f"minecraft/instances/{new_name}")
  615.             config = configparser.ConfigParser()
  616.             config.read(f"minecraft/instances/{new_name}/instance.cfg")
  617.             if "General" in config:
  618.                 config["General"]["name"] = new_name
  619.                 with open(f"minecraft/instances/{new_name}/instance.cfg", "w") as f:
  620.                     config.write(f)
  621.         for tile in self.groups[group_name]:
  622.             if tile.name == old_name:
  623.                 tile.name = new_name
  624.                 break
  625.         self.save_instgroups()
  626.  
  627.     def update_instance_group(self, instance_name, old_group, new_group):
  628.         self.save_instgroups()
  629.  
  630.     def create_toolbar(self):
  631.         toolbar = QToolBar()
  632.         self.addToolBar(Qt.ToolBarArea.TopToolBarArea, toolbar)
  633.  
  634.         logo_btn = QPushButton("MCLauncher")
  635.         logo_btn.setStyleSheet("font-weight: bold; padding: 4px 10px;")
  636.         toolbar.addWidget(logo_btn)
  637.         toolbar.addSeparator()
  638.  
  639.         add_action = QAction(QIcon.fromTheme("list-add"), "Dodaj Instalację", self)
  640.         add_action.triggered.connect(self.open_add_instance_dialog)
  641.         toolbar.addAction(add_action)
  642.  
  643.         add_group_action = QAction(QIcon.fromTheme("folder-new"), "Dodaj Grupę", self)
  644.         add_group_action.triggered.connect(self.open_add_group_dialog)
  645.         toolbar.addAction(add_group_action)
  646.  
  647.         folder_menu = QMenu("Foldery", self)
  648.         folder_menu.addAction("Folder instancji").triggered.connect(lambda: QDesktopServices.openUrl(QUrl.fromLocalFile("minecraft/instances")))
  649.         folder_menu.addAction("Folder modów").triggered.connect(lambda: QDesktopServices.openUrl(QUrl.fromLocalFile("minecraft/mods")))
  650.         folder_menu.addAction("Folder logów").triggered.connect(lambda: QDesktopServices.openUrl(QUrl.fromLocalFile("minecraft/logs")))
  651.         folder_menu.addAction("Folder assets").triggered.connect(lambda: QDesktopServices.openUrl(QUrl.fromLocalFile("minecraft/assets")))
  652.         folder_menu.addAction("Folder bibliotek").triggered.connect(lambda: QDesktopServices.openUrl(QUrl.fromLocalFile("minecraft/libraries")))
  653.         folder_menu.addAction("Folder Javy").triggered.connect(lambda: QDesktopServices.openUrl(QUrl.fromLocalFile("minecraft/javas")))
  654.         folder_btn = QPushButton("Foldery")
  655.         folder_btn.setMenu(folder_menu)
  656.         toolbar.addWidget(folder_btn)
  657.  
  658.         settings_action = QAction(QIcon.fromTheme("preferences-system"), "Ustawienia", self)
  659.         settings_action.triggered.connect(self.open_settings_dialog)
  660.         toolbar.addAction(settings_action)
  661.  
  662.         help_action = QAction(QIcon.fromTheme("help-about"), "Pomoc", self)
  663.         toolbar.addAction(help_action)
  664.  
  665.         update_action = QAction(QIcon.fromTheme("view-refresh"), "Aktualizuj", self)
  666.         update_action.triggered.connect(self.refresh_launcher)
  667.         toolbar.addAction(update_action)
  668.  
  669.         spacer = QWidget()
  670.         spacer.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
  671.         toolbar.addWidget(spacer)
  672.  
  673.         self.user_btn = QPushButton(self.username or "Dodaj użytkownika")
  674.         toolbar.addWidget(self.user_btn)
  675.  
  676.     def create_main_area(self):
  677.         central_widget = QWidget()
  678.         self.setCentralWidget(central_widget)
  679.  
  680.         layout = QHBoxLayout()
  681.         self.main_area = QScrollArea()
  682.         self.main_area.setWidgetResizable(True)
  683.         content_widget = QWidget()
  684.         self.grid_layout = QVBoxLayout()
  685.  
  686.         for group in self.groups.keys():
  687.             group_section = GroupSection(group, self.groups[group], self)
  688.             self.group_widgets[group] = {"header": None, "section": group_section}
  689.  
  690.         self.refresh_instances()
  691.         content_widget.setLayout(self.grid_layout)
  692.         self.main_area.setWidget(content_widget)
  693.         layout.addWidget(self.main_area, stretch=3)
  694.  
  695.         self.right_panel = QWidget()
  696.         right_layout = QVBoxLayout()
  697.         self.right_icon = QLabel("🟩")
  698.         self.right_icon.setAlignment(Qt.AlignmentFlag.AlignCenter)
  699.         self.right_icon.setStyleSheet("font-size: 64px; border: none;")
  700.         right_layout.addWidget(self.right_icon)
  701.  
  702.         self.launch_btn = QPushButton("Uruchom")
  703.         self.launch_btn.clicked.connect(self.launch_selected)
  704.         right_layout.addWidget(self.launch_btn)
  705.  
  706.         self.kill_btn = QPushButton("Zakończ")
  707.         self.kill_btn.clicked.connect(self.kill_selected)
  708.         right_layout.addWidget(self.kill_btn)
  709.  
  710.         self.edit_btn = QPushButton("Edytuj")
  711.         self.edit_btn.clicked.connect(self.edit_selected)
  712.         right_layout.addWidget(self.edit_btn)
  713.  
  714.         self.change_group_btn = QPushButton("Zmień grupę")
  715.         right_layout.addWidget(self.change_group_btn)
  716.  
  717.         self.folder_btn = QPushButton("Folder")
  718.         self.folder_btn.clicked.connect(self.open_instance_folder)
  719.         right_layout.addWidget(self.folder_btn)
  720.  
  721.         self.export_btn = QPushButton("Eksportuj")
  722.         right_layout.addWidget(self.export_btn)
  723.  
  724.         self.copy_btn = QPushButton("Kopiuj")
  725.         right_layout.addWidget(self.copy_btn)
  726.  
  727.         self.delete_btn = QPushButton("Usuń")
  728.         self.delete_btn.clicked.connect(self.delete_instance)
  729.         right_layout.addWidget(self.delete_btn)
  730.  
  731.         self.shortcut_btn = QPushButton("Utwórz skrót")
  732.         right_layout.addWidget(self.shortcut_btn)
  733.  
  734.         self.right_panel.setLayout(right_layout)
  735.         layout.addWidget(self.right_panel, stretch=1)
  736.         central_widget.setLayout(layout)
  737.  
  738.     def create_status_bar(self):
  739.         self.status_bar = self.statusBar()
  740.         self.progress_bar = QProgressBar()
  741.         self.progress_bar.setFixedHeight(20)
  742.         self.progress_bar.setFixedWidth(400)
  743.         self.progress_bar.setTextVisible(False)
  744.         self.status_bar.addPermanentWidget(self.progress_bar, 1)
  745.         self.status_label = QLabel("Gotowy")
  746.         self.status_bar.addPermanentWidget(self.status_label, 1)
  747.         self.download_info = QLabel("")
  748.         self.status_bar.addPermanentWidget(self.download_info, 2)
  749.         self.progress_bar.hide()
  750.  
  751.     def refresh_instances(self):
  752.         for i in reversed(range(self.grid_layout.count())):
  753.             widget = self.grid_layout.itemAt(i).widget()
  754.             if widget:
  755.                 self.grid_layout.removeWidget(widget)
  756.                 widget.hide()
  757.  
  758.         for group in self.groups.keys():
  759.             if not self.group_widgets[group]["header"]:
  760.                 header_widget = QWidget()
  761.                 header_layout = QHBoxLayout()
  762.                 collapse_btn = QPushButton("▼" if group not in self.collapsed_groups else "▶")
  763.                 collapse_btn.setFixedSize(20, 20)
  764.                 collapse_btn.clicked.connect(lambda checked, g=group: self.toggle_group(g))
  765.                 header_layout.addWidget(collapse_btn)
  766.                 group_label = QLabel(group)
  767.                 header_layout.addWidget(group_label)
  768.                 header_widget.setLayout(header_layout)
  769.                 self.group_widgets[group]["header"] = header_widget
  770.             self.grid_layout.addWidget(self.group_widgets[group]["header"])
  771.             self.group_widgets[group]["header"].show()
  772.  
  773.             if group not in self.collapsed_groups:
  774.                 self.grid_layout.addWidget(self.group_widgets[group]["section"])
  775.                 self.group_widgets[group]["section"].show()
  776.  
  777.     def toggle_group(self, group):
  778.         if group in self.collapsed_groups:
  779.             self.collapsed_groups.remove(group)
  780.         else:
  781.             self.collapsed_groups.add(group)
  782.         self.save_instgroups()
  783.         self.refresh_instances()
  784.  
  785.     def update_right_panel(self):
  786.         if self.selected_tile:
  787.             self.right_icon.setText(self.selected_tile.icon.text())
  788.         else:
  789.             self.right_icon.setText("🟩")
  790.  
  791.     def launch_selected(self):
  792.         if not self.selected_tile:
  793.             QMessageBox.warning(self, "Błąd", "Nie wybrano instancji!")
  794.             return
  795.  
  796.         instance_name = self.selected_tile.name
  797.         instance_dir = f"minecraft/instances/{instance_name}"
  798.         if not os.path.exists(instance_dir):
  799.             QMessageBox.warning(self, "Błąd", f"Folder instancji {instance_name} nie istnieje!")
  800.             return
  801.  
  802.         version = self.selected_tile.version
  803.         version_info = next((v for v in self.versions_data["versions"] if v["id"] == version), None)
  804.         if not version_info:
  805.             QMessageBox.warning(self, "Błąd", f"Nie można znaleźć informacji o wersji {version}!")
  806.             return
  807.  
  808.         try:
  809.             version_details = self.get_version_details(version_info["url"])
  810.         except Exception as e:
  811.             QMessageBox.warning(self, "Błąd", f"Nie można pobrać szczegółów wersji: {str(e)}")
  812.             return
  813.  
  814.         missing_files = self.check_required_files(version_details, instance_name)
  815.         if missing_files:
  816.             total_missing = sum(len(files) for files in missing_files.values())
  817.             reply = QMessageBox.question(
  818.                 self, "Pobieranie plików",
  819.                 f"Brakuje {total_missing} plików (assets, libraries, natives). Czy chcesz je pobrać?",
  820.                 QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
  821.             )
  822.             if reply == QMessageBox.StandardButton.Yes:
  823.                 self.download_required_files(missing_files, instance_name, version_details)
  824.             return
  825.  
  826.         self.run_minecraft(instance_name, version_details)
  827.  
  828.     def get_version_details(self, version_url):
  829.         version_id = version_url.split("/")[-2]
  830.         version_file = f"minecraft/versions/{version_id}/{version_id}.json"
  831.         if os.path.exists(version_file):
  832.             with open(version_file, "r") as f:
  833.                 return json.load(f)
  834.         response = requests.get(version_url)
  835.         response.raise_for_status()
  836.         version_details = response.json()
  837.         os.makedirs(os.path.dirname(version_file), exist_ok=True)
  838.         with open(version_file, "w") as f:
  839.             json.dump(version_details, f)
  840.         return version_details
  841.  
  842.     def check_required_files(self, version_details, instance_name):
  843.         missing_files = {'assets': {}, 'libraries': {}, 'natives': {}}
  844.         download_state = load_download_state()
  845.  
  846.         # Sprawdzanie client.jar
  847.         version_id = version_details['id']
  848.         client_jar = os.path.join("minecraft", "versions", version_id, f"{version_id}.jar")
  849.         if not os.path.exists(client_jar):
  850.             client_url = version_details["downloads"]["client"]["url"]
  851.             missing_files['assets'][client_jar] = client_url
  852.  
  853.         # Sprawdzanie bibliotek
  854.         for library in version_details.get("libraries", []):
  855.             if "rules" in library and not self.check_library_rules(library["rules"]):
  856.                 continue
  857.             path = self.get_library_path(library)
  858.             url = self.get_library_url(library)
  859.             if not os.path.exists(path):
  860.                 if 'libraries' not in download_state or path not in download_state.get('libraries', {}):
  861.                     missing_files['libraries'][path] = url
  862.                 elif download_state['libraries'].get(path) == url and not os.path.exists(path):
  863.                     missing_files['libraries'][path] = url
  864.  
  865.         # Sprawdzanie natywnych bibliotek
  866.         for library in version_details.get("libraries", []):
  867.             if "natives" in library and "rules" in library and self.check_library_rules(library["rules"]):
  868.                 native = library["natives"].get(platform.system().lower())
  869.                 if native:
  870.                     artifact = library["downloads"]["classifiers"].get(native)
  871.                     if artifact:
  872.                         path = os.path.join("minecraft", "instances", instance_name, "natives", os.path.basename(artifact["path"]))
  873.                         if not os.path.exists(path):
  874.                             if 'natives' not in download_state or path not in download_state.get('natives', {}):
  875.                                 missing_files['natives'][path] = artifact["url"]
  876.                             elif download_state['natives'].get(path) == artifact["url"] and not os.path.exists(path):
  877.                                 missing_files['natives'][path] = artifact["url"]
  878.  
  879.         # Sprawdzanie asset index
  880.         asset_index = version_details.get("assetIndex", {}).get("id", "legacy")
  881.         asset_index_file = os.path.join("minecraft", "assets", "indexes", f"{asset_index}.json")
  882.         if not os.path.exists(asset_index_file):
  883.             if 'assets' not in download_state or asset_index_file not in download_state.get('assets', {}):
  884.                 missing_files['assets'][asset_index_file] = version_details["assetIndex"]["url"]
  885.             elif download_state['assets'].get(asset_index_file) == version_details["assetIndex"]["url"] and not os.path.exists(asset_index_file):
  886.                 missing_files['assets'][asset_index_file] = version_details["assetIndex"]["url"]
  887.  
  888.         # Sprawdzanie obiektów assets
  889.         if os.path.exists(asset_index_file):
  890.             with open(asset_index_file, "r") as f:
  891.                 asset_index = json.load(f)
  892.             objects = asset_index.get("objects", {})
  893.             for asset_name, asset_data in objects.items():
  894.                 hash = asset_data["hash"]
  895.                 asset_path = os.path.join("minecraft", "assets", "objects", hash[:2], hash)
  896.                 url = f"https://resources.download.minecraft.net/{hash[:2]}/{hash}"
  897.                 if not os.path.exists(asset_path):
  898.                     if 'assets' not in download_state or asset_path not in download_state.get('assets', {}):
  899.                         missing_files['assets'][asset_path] = url
  900.                     elif download_state['assets'].get(asset_path) == url and not os.path.exists(asset_path):
  901.                         missing_files['assets'][asset_path] = url
  902.  
  903.         return missing_files
  904.  
  905.     def check_library_rules(self, rules):
  906.         for rule in rules:
  907.             if rule.get("action") == "allow":
  908.                 if "os" in rule and rule["os"]["name"] != platform.system().lower():
  909.                     return False
  910.             elif rule.get("action") == "disallow":
  911.                 if "os" in rule and rule["os"]["name"] == platform.system().lower():
  912.                     return False
  913.         return True
  914.  
  915.     def get_library_path(self, library):
  916.         lib_name = library["name"]
  917.         parts = lib_name.split(":")
  918.         base_path = os.path.join("minecraft", "libraries", *parts[0].split("."), parts[1], parts[2])
  919.         artifact = library.get("downloads", {}).get("artifact", {})
  920.         if artifact and "path" in artifact:
  921.             return os.path.join(base_path, artifact["path"])
  922.         return os.path.join(base_path, f"{parts[1]}-{parts[2]}.jar")
  923.  
  924.     def get_library_url(self, library):
  925.         artifact = library.get("downloads", {}).get("artifact", {})
  926.         if artifact and "url" in artifact:
  927.             return artifact["url"]
  928.         lib_name = library["name"]
  929.         parts = lib_name.split(":")
  930.         base_url = f"https://libraries.minecraft.net/{parts[0].replace('.', '/')}/{parts[1]}/{parts[2]}"
  931.         return f"{base_url}/{parts[1]}-{parts[2]}.jar"
  932.  
  933.     def download_required_files(self, files_to_download, instance_name, version_details):
  934.         self.current_downloads = files_to_download
  935.         for download_type, files in files_to_download.items():
  936.             if files:
  937.                 self.download_counts[download_type]['total'] = len(files)
  938.                 self.download_counts[download_type]['completed'] = 0
  939.                 self.download_worker = DownloadWorker(files, download_type)
  940.                 self.download_worker.progress_updated.connect(self.update_download_progress)
  941.                 self.download_worker.download_finished.connect(self.download_finished)
  942.                 self.download_worker.file_downloaded.connect(self.file_downloaded)
  943.                 self.progress_bar.show()
  944.                 self.status_label.setText(f"[{download_type}]: pobieranie 0% | 0 / {len(files)}")
  945.                 self.download_worker.start()
  946.                 break
  947.  
  948.     def update_download_progress(self, download_type, percent, completed, total, filename, downloaded_mb, total_mb):
  949.         self.download_counts[download_type]['completed'] = completed
  950.         self.download_counts[download_type]['total'] = total
  951.         self.progress_bar.setValue(percent)
  952.         self.status_label.setText(f"[{download_type}]: pobieranie {percent}% | {completed} / {total}")
  953.         self.download_info.setText(f"{filename}: {downloaded_mb} mb / {total_mb} mb")
  954.  
  955.     def file_downloaded(self, download_type, file_path):
  956.         if "assets/indexes" in file_path:
  957.             self.process_asset_index(file_path)
  958.  
  959.     def process_asset_index(self, index_file):
  960.         try:
  961.             with open(index_file, "r") as f:
  962.                 asset_index = json.load(f)
  963.             objects = asset_index.get("objects", {})
  964.             files_to_download = {}
  965.             for asset_name, asset_data in objects.items():
  966.                 hash = asset_data["hash"]
  967.                 asset_path = os.path.join("minecraft", "assets", "objects", hash[:2], hash)
  968.                 if not os.path.exists(asset_path):
  969.                     url = f"https://resources.download.minecraft.net/{hash[:2]}/{hash}"
  970.                     files_to_download[asset_path] = url
  971.             if files_to_download:
  972.                 self.current_downloads['assets'].update(files_to_download)
  973.                 self.download_counts['assets']['total'] += len(files_to_download)
  974.                 self.download_worker.files_to_download.update(files_to_download)
  975.                 self.progress_bar.setMaximum(self.download_counts['assets']['total'] * 100)
  976.         except Exception as e:
  977.             logging.error(f"Błąd podczas przetwarzania asset index: {str(e)}")
  978.  
  979.     def download_finished(self, success, message):
  980.         self.progress_bar.hide()
  981.         self.status_label.setText(message)
  982.         self.download_info.setText("")
  983.         if success:
  984.             instance_name = self.selected_tile.name
  985.             instance_dir = f"minecraft/instances/{instance_name}"
  986.             version = self.selected_tile.version
  987.             version_info = next((v for v in self.versions_data["versions"] if v["id"] == version), None)
  988.             version_details = self.get_version_details(version_info["url"])
  989.             self.run_minecraft(instance_name, version_details)
  990.         else:
  991.             QMessageBox.warning(self, "Błąd pobierania", message)
  992.  
  993.     def run_minecraft(self, instance_name, version_details):
  994.         instance_dir = f"minecraft/instances/{instance_name}"
  995.         version_id = version_details["id"]
  996.         java_path = self.find_java_for_version(version_details)
  997.         if not java_path:
  998.             QMessageBox.warning(self, "Błąd", "Nie znaleziono odpowiedniej wersji Java!")
  999.             return
  1000.  
  1001.         args = self.prepare_launch_arguments(java_path, instance_name, version_details)
  1002.         self.minecraft_runner = MinecraftRunner(args, instance_dir)
  1003.         self.minecraft_runner.output_received.connect(self.handle_minecraft_output)
  1004.         self.minecraft_runner.finished.connect(self.handle_minecraft_finished)
  1005.         self.minecraft_runner.start()
  1006.  
  1007.         self.selected_tile.is_running = True
  1008.         self.selected_tile.status_icon.setText("▶")
  1009.         self.status_label.setText(f"Uruchomiono {instance_name}")
  1010.  
  1011.         if os.path.exists(f"{instance_dir}/instance.cfg"):
  1012.             config = configparser.ConfigParser()
  1013.             config.read(f"{instance_dir}/instance.cfg")
  1014.             if "General" in config:
  1015.                 config["General"]["lastLaunchTime"] = str(int(datetime.now().timestamp() * 1000))
  1016.                 with open(f"{instance_dir}/instance.cfg", "w") as f:
  1017.                     config.write(f)
  1018.         logging.info(f"Uruchomiono instancję: {instance_name}")
  1019.         print(f"[INFO] Uruchomiono instancję: {instance_name}")
  1020.  
  1021.     def find_java_for_version(self, version_details):
  1022.         if not self.settings.get("auto_java", True) and self.settings.get("java_path"):
  1023.             return self.settings["java_path"]
  1024.         java_path = JavaFinder.find_java_for_version(version_details)
  1025.         if java_path:
  1026.             return java_path
  1027.         reply = QMessageBox.question(
  1028.             self, "Brak Java",
  1029.             "Nie znaleziono odpowiedniej wersji Java. Czy chcesz pobrać wymaganą wersję?",
  1030.             QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
  1031.         )
  1032.         if reply == QMessageBox.StandardButton.Yes:
  1033.             self.download_java(version_details.get("javaVersion", {}).get("majorVersion", 8))
  1034.             return None
  1035.         return None
  1036.  
  1037.     def download_java(self, java_version):
  1038.         QMessageBox.information(self, "Informacja", "Pobieranie Java będzie dostępne w przyszłej wersji.")
  1039.  
  1040.     def prepare_launch_arguments(self, java_path, instance_name, version_details):
  1041.         args = [java_path]
  1042.         jvm_args = version_details.get("arguments", {}).get("jvm", [])
  1043.         for arg in jvm_args:
  1044.             if isinstance(arg, dict):
  1045.                 continue
  1046.             args.append(arg.replace("${natives_directory}", f"minecraft/instances/{instance_name}/natives")
  1047.                       .replace("${launcher_name}", "MCLauncher")
  1048.                       .replace("${launcher_version}", "1.0.0"))
  1049.         classpath = self.build_classpath(version_details, instance_name)
  1050.         args.extend(["-cp", classpath])
  1051.         main_class = version_details.get("mainClass", "net.minecraft.client.main.Main")
  1052.         args.append(main_class)
  1053.         game_args = version_details.get("arguments", {}).get("game", [])
  1054.         for arg in game_args:
  1055.             if isinstance(arg, dict):
  1056.                 continue
  1057.             args.append(arg.replace("${version_name}", version_details["id"])
  1058.                       .replace("${game_directory}", f"minecraft/instances/{instance_name}")
  1059.                       .replace("${assets_root}", "minecraft/assets")
  1060.                       .replace("${assets_index_name}", version_details.get("assetIndex", {}).get("id", "legacy"))
  1061.                       .replace("${auth_player_name}", self.username or "Player")
  1062.                       .replace("${version_type}", version_details.get("type", "release")))
  1063.         return args
  1064.  
  1065.     def build_classpath(self, version_details, instance_name):
  1066.         classpath = []
  1067.         version_id = version_details["id"]
  1068.         classpath.append(f"minecraft/versions/{version_id}/{version_id}.jar")
  1069.         for library in version_details.get("libraries", []):
  1070.             if "rules" in library and not self.check_library_rules(library["rules"]):
  1071.                 continue
  1072.             path = self.get_library_path(library)
  1073.             if os.path.exists(path):
  1074.                 classpath.append(path)
  1075.         separator = ";" if platform.system() == "Windows" else ":"
  1076.         return separator.join(classpath)
  1077.  
  1078.     def handle_minecraft_output(self, output):
  1079.         print(f"[Minecraft] {output}")
  1080.  
  1081.     def handle_minecraft_finished(self, exit_code):
  1082.         if self.selected_tile:
  1083.             self.selected_tile.is_running = False
  1084.             self.selected_tile.status_icon.setText("")
  1085.         self.status_label.setText(f"Gra zakończona z kodem {exit_code}")
  1086.         logging.info(f"Gra zakończona z kodem {exit_code}")
  1087.  
  1088.     def kill_selected(self):
  1089.         if self.selected_tile and self.selected_tile.is_running:
  1090.             if self.minecraft_runner:
  1091.                 self.minecraft_runner.stop()
  1092.             self.selected_tile.is_running = False
  1093.             self.selected_tile.status_icon.setText("")
  1094.             self.status_label.setText("Zatrzymano grę")
  1095.             logging.info(f"Zakończono instancję: {self.selected_tile.name}")
  1096.             print(f"[INFO] Zakończono instancję: {self.selected_tile.name}")
  1097.  
  1098.     def edit_selected(self):
  1099.         if self.selected_tile:
  1100.             logging.info(f"Edytowanie instancji: {self.selected_tile.name}")
  1101.             print(f"[INFO] Edytowanie instancji: {self.selected_tile.name}")
  1102.  
  1103.     def open_instance_folder(self):
  1104.         if self.selected_tile:
  1105.             instance_dir = f"minecraft/instances/{self.selected_tile.name}"
  1106.             if os.path.exists(instance_dir):
  1107.                 QDesktopServices.openUrl(QUrl.fromLocalFile(instance_dir))
  1108.             logging.info(f"Otwieranie folderu instancji: {self.selected_tile.name}")
  1109.             print(f"[INFO] Otwieranie folderu instancji: {self.selected_tile.name}")
  1110.  
  1111.     def delete_instance(self):
  1112.         if not self.selected_tile:
  1113.             return
  1114.         instance_name = self.selected_tile.name
  1115.         reply = QMessageBox.question(
  1116.             self, "Usuwanie instancji",
  1117.             f"Czy na pewno chcesz usunąć instancję '{instance_name}'?",
  1118.             QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
  1119.         )
  1120.         if reply == QMessageBox.StandardButton.Yes:
  1121.             for group, tiles in self.groups.items():
  1122.                 if self.selected_tile in tiles:
  1123.                     tiles.remove(self.selected_tile)
  1124.                     break
  1125.             instance_dir = f"minecraft/instances/{instance_name}"
  1126.             if os.path.exists(instance_dir):
  1127.                 shutil.rmtree(instance_dir)
  1128.             self.save_instgroups()
  1129.             self.refresh_instances()
  1130.             self.selected_tile = None
  1131.             self.update_right_panel()
  1132.             logging.info(f"Usunięto instancję: {instance_name}")
  1133.             print(f"[INFO] Usunięto instancję: {instance_name}")
  1134.  
  1135.     def open_add_instance_dialog(self):
  1136.         if not self.versions_data:
  1137.             QMessageBox.warning(self, "Błąd", "Nie można załadować listy wersji Minecraft!")
  1138.             return
  1139.         dialog = AddInstanceDialog(list(self.groups.keys()), self.versions_data, self)
  1140.         if dialog.exec() == QDialog.DialogCode.Accepted:
  1141.             instance_name = dialog.instance_name.text()
  1142.             version_id = dialog.version_combo.currentData()
  1143.             group_name = dialog.group_combo.currentText()
  1144.             new_tile = InstanceTile(instance_name, version_id, group_name, self)
  1145.             self.groups[group_name].append(new_tile)
  1146.             self.group_widgets[group_name]["section"] = GroupSection(group_name, self.groups[group_name], self)
  1147.             self.create_instance_folder(instance_name, version_id)
  1148.             self.save_instgroups()
  1149.             self.refresh_instances()
  1150.             logging.info(f"Dodano instancję: {instance_name}, wersja: {version_id}")
  1151.             print(f"[INFO] Dodano instancję: {instance_name}, wersja: {version_id}")
  1152.  
  1153.     def create_instance_folder(self, instance_name, version_id):
  1154.         instance_dir = f"minecraft/instances/{instance_name}"
  1155.         os.makedirs(instance_dir, exist_ok=True)
  1156.         os.makedirs(f"{instance_dir}/minecraft", exist_ok=True)
  1157.         os.makedirs(f"{instance_dir}/natives", exist_ok=True)
  1158.         config = configparser.ConfigParser()
  1159.         config['General'] = {
  1160.             'ConfigVersion': '1.2',
  1161.             'ManagedPack': 'false',
  1162.             'iconKey': 'default',
  1163.             'name': instance_name,
  1164.             'InstanceType': version_id,
  1165.             'lastLaunchTime': str(int(datetime.now().timestamp() * 1000)),
  1166.             'lastTimePlayed': '0',
  1167.             'totalTimePlayed': '0'
  1168.         }
  1169.         with open(f"{instance_dir}/instance.cfg", "w") as config_file:
  1170.             config.write(config_file)
  1171.  
  1172.     def open_add_group_dialog(self):
  1173.         dialog = AddGroupDialog()
  1174.         if dialog.exec() == QDialog.DialogCode.Accepted:
  1175.             group_name = dialog.group_name.text()
  1176.             if group_name and group_name not in self.groups:
  1177.                 self.groups[group_name] = []
  1178.                 self.group_widgets[group_name] = {
  1179.                     "header": None,
  1180.                     "section": GroupSection(group_name, self.groups[group_name], self)
  1181.                 }
  1182.                 self.save_instgroups()
  1183.                 self.refresh_instances()
  1184.                 logging.info(f"Dodano grupę: {group_name}")
  1185.                 print(f"[INFO] Dodano grupę: {group_name}")
  1186.  
  1187.     def open_settings_dialog(self):
  1188.         dialog = SettingsDialog(self)
  1189.         if dialog.exec() == QDialog.DialogCode.Accepted:
  1190.             self.username = dialog.username_edit.text()
  1191.             self.theme = dialog.theme_combo.currentText()
  1192.             self.settings = {
  1193.                 "auto_java": dialog.auto_java_check.isChecked(),
  1194.                 "java_path": dialog.java_path_edit.text()
  1195.             }
  1196.             self.user_btn.setText(self.username or "Dodaj użytkownika")
  1197.             self.save_settings()
  1198.             self.apply_theme()
  1199.             logging.info(f"Zaktualizowano ustawienia: użytkownik={self.username}, motyw={self.theme}")
  1200.             print(f"[INFO] Zaktualizowano ustawienia: użytkownik={self.username}, motyw={self.theme}")
  1201.  
  1202.     def load_settings(self):
  1203.         try:
  1204.             with open("minecraft/settings.json", "r") as f:
  1205.                 settings = json.load(f)
  1206.                 self.username = settings.get("username", None)
  1207.                 self.theme = settings.get("theme", "Jasny")
  1208.                 self.settings = settings.get("settings", {})
  1209.         except FileNotFoundError:
  1210.             self.save_settings()
  1211.  
  1212.     def save_settings(self):
  1213.         settings = {
  1214.             "username": self.username,
  1215.             "theme": self.theme,
  1216.             "settings": self.settings
  1217.         }
  1218.         with open("minecraft/settings.json", "w") as f:
  1219.             json.dump(settings, f, indent=4)
  1220.  
  1221.     def apply_theme(self):
  1222.         if self.theme == "Ciemny":
  1223.             self.setStyleSheet("""
  1224.                QMainWindow { background-color: #2b2b2b; color: #ffffff; }
  1225.                QWidget { background-color: #2b2b2b; color: #ffffff; }
  1226.                QPushButton { background-color: #3c3c3c; border: 1px solid #555555; color: #ffffff; }
  1227.                QLineEdit, QComboBox { background-color: #3c3c3c; color: #ffffff; }
  1228.                QScrollArea { background-color: #2b2b2b; }
  1229.                QProgressBar { background-color: #3c3c3c; border: 1px solid #555555; }
  1230.            """)
  1231.         else:
  1232.             self.setStyleSheet("")
  1233.  
  1234.     def refresh_launcher(self):
  1235.         self.fetch_minecraft_versions()
  1236.         QMessageBox.information(self, "Odświeżono", "Lista wersji Minecraft została odświeżona.")
  1237.  
  1238. if __name__ == "__main__":
  1239.     app = QApplication(sys.argv)
  1240.     launcher = MCLauncher()
  1241.     launcher.show()
  1242.     sys.exit(app.exec())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement