PixelLauncher 2.1.0 — стабильная сборка
Офлайн-лаунчер Minecraft: аккаунты без Microsoft, установка версий и лоадеров (Fabric, Quilt, Forge, NeoForge), каталог модпаков Modrinth, поиск и обновление модов, разбор вылетов, системный трей, автозагрузка, плагины, прокси и зеркала.
This commit is contained in:
commit
ec6f13ddcc
33 files changed
+10445
No files matched your search
@@ -0,0 +1,99 @@
|
||||
"""Запуск игры (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
|
||||
Reference in new issue
Block a user