"""Запуск игры (offline-mode).""" import hashlib import os import subprocess import threading import time from .i18n import T CREATE_NO_WINDOW = 0x08000000 if os.name == "nt" else 0 def offline_uuid(username): """UUID, который Mojang генерирует для оффлайн-игроков.""" h = hashlib.md5(("OfflinePlayer:" + username).encode("utf-8")).digest() b = bytearray(h) b[6] = (b[6] & 0x0F) | 0x30 # version 3 b[8] = (b[8] & 0x3F) | 0x80 # RFC 4122 variant return "".join(f"{x:02x}" for x in b) def launch(game_dir, cmd, log=None, log_path=None, on_line=None, on_exit=None): """Запускает процесс игры. Возвращает Popen. log_path — файл для полного вывода игры; on_line — вызывается на каждую строку вывода (живой лог); on_exit — вызывается после завершения процесса (после чтения вывода). """ if log: log("Команда: " + " ".join(cmd)) stream = on_line is not None or log_path is not None f = None if log_path: try: os.makedirs(os.path.dirname(log_path), exist_ok=True) f = open(log_path, "ab") f.write(("\n=== PixelLauncher: " + time.strftime("%Y-%m-%d %H:%M:%S") + " ===\n" + "Команда: " + " ".join(cmd) + "\n").encode("utf-8", "replace")) except OSError: f = None proc = subprocess.Popen( cmd, cwd=game_dir, stdout=subprocess.PIPE if stream else subprocess.DEVNULL, stderr=subprocess.STDOUT if stream else subprocess.DEVNULL, text=True, encoding="utf-8", errors="replace", bufsize=1, creationflags=CREATE_NO_WINDOW, ) def _pump(): """Читает вывод игры: пишет в файл и отдаёт строки построчно.""" try: for line in proc.stdout: if f is not None: try: f.write(line.encode("utf-8", "replace")) except ValueError: pass if on_line: try: on_line(line.rstrip("\r\n")) except Exception: # noqa: BLE001 pass except (ValueError, OSError): pass finally: if f is not None: try: f.close() except OSError: pass pump = None if stream: pump = threading.Thread(target=_pump, daemon=True) pump.start() elif f is not None: f.close() def _wait(): code = proc.wait() if pump is not None: # дать выводу дочитаться pump.join(timeout=10) if on_exit: try: on_exit(code) except Exception: # noqa: BLE001 pass threading.Thread(target=_wait, daemon=True).start() if log: log(T("Игра запущена, PID ") + str(proc.pid) + ". " + T("Приятной игры!")) return proc