Files
PixelLauncher/launcher/launch.py
T
kosta2014p ec6f13ddcc PixelLauncher 2.1.0 — стабильная сборка
Офлайн-лаунчер Minecraft: аккаунты без Microsoft, установка версий и лоадеров
(Fabric, Quilt, Forge, NeoForge), каталог модпаков Modrinth, поиск и обновление
модов, разбор вылетов, системный трей, автозагрузка, плагины, прокси и зеркала.
2026-10-18 22:44:19 +07:00

100 lines
3.3 KiB
Python

"""Запуск игры (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