PixelLauncher 2.1.0 — стабильная сборка

Офлайн-лаунчер Minecraft: аккаунты без Microsoft, установка версий и лоадеров
(Fabric, Quilt, Forge, NeoForge), каталог модпаков Modrinth, поиск и обновление
модов, разбор вылетов, системный трей, автозагрузка, плагины, прокси и зеркала.
This commit is contained in:
kosta2014p committed 2026-10-18 22:44:19 +07:00
commit ec6f13ddcc
33 files changed
+10445

No files matched your search

+111
View File
@@ -0,0 +1,111 @@
"""Работа с манифестом версий Mojang (piston-meta)."""
import json
import os
import time
from . import http
MANIFEST_URL = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
MANIFEST_MAX_AGE = 3600 # свежий кэш не перекачиваем (час)
_memory_cache = None
_skip_ttl_once = False
def _cache_dir(game_dir):
d = os.path.join(game_dir, ".launcher")
os.makedirs(d, exist_ok=True)
return d
def invalidate():
"""Сбросить кэш: следующий get_manifest скачает манифест заново."""
global _memory_cache, _skip_ttl_once
_memory_cache = None
_skip_ttl_once = True
def get_manifest(game_dir, net=True, max_age=MANIFEST_MAX_AGE):
"""Возвращает манифест версий.
Свежий кэш на диске используется сразу, без обращения к сети.
Сеть -> кэш на диске -> ошибка.
"""
global _memory_cache, _skip_ttl_once
if _memory_cache is not None:
return _memory_cache
cache = os.path.join(_cache_dir(game_dir), "version_manifest.json")
force = _skip_ttl_once
_skip_ttl_once = False
fresh = False
if os.path.isfile(cache):
try:
fresh = (time.time() - os.path.getmtime(cache)) < max_age
except OSError:
fresh = False
if net and (force or not fresh):
try:
data = json.loads(http.get_bytes(MANIFEST_URL, timeout=20))
with open(cache, "w", encoding="utf-8") as f:
json.dump(data, f)
_memory_cache = data
return data
except Exception:
pass
if os.path.isfile(cache):
with open(cache, encoding="utf-8") as f:
_memory_cache = json.load(f)
return _memory_cache
raise RuntimeError("Нет доступа к сети и нет кэша манифеста версий")
def version_url(manifest, vid):
for v in manifest.get("versions", []):
if v.get("id") == vid:
return v.get("url")
return None
def local_versions(game_dir):
"""Идентификаторы версий, установленных локально (Fabric, Quilt и т.п.)."""
root = os.path.join(game_dir, "versions")
out = []
try:
for name in sorted(os.listdir(root)):
if os.path.isfile(os.path.join(root, name, name + ".json")):
out.append(name)
except OSError:
pass
return out
def fetch_version_json(game_dir, vid, net=True, max_age=21600):
"""JSON конкретной версии: свежий кэш -> сеть -> versions/{id}/{id}.json."""
path = os.path.join(game_dir, "versions", vid, vid + ".json")
fresh = False
if os.path.isfile(path):
try:
fresh = (time.time() - os.path.getmtime(path)) < max_age
except OSError:
fresh = False
if net and not fresh:
try:
manifest = get_manifest(game_dir, net=True)
url = version_url(manifest, vid)
if not url:
raise RuntimeError(f"Версия {vid} не найдена в манифесте")
raw = http.get_bytes(url, timeout=20)
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "wb") as f:
f.write(raw)
return json.loads(raw)
except Exception:
pass
if os.path.isfile(path):
with open(path, encoding="utf-8") as f:
return json.load(f)
raise RuntimeError(f"Не удалось получить JSON версии {vid}")