Офлайн-лаунчер Minecraft: аккаунты без Microsoft, установка версий и лоадеров (Fabric, Quilt, Forge, NeoForge), каталог модпаков Modrinth, поиск и обновление модов, разбор вылетов, системный трей, автозагрузка, плагины, прокси и зеркала.
176 lines
5.3 KiB
Python
176 lines
5.3 KiB
Python
"""Поиск и выбор Java."""
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
import struct
|
|
import subprocess
|
|
import sys
|
|
|
|
CREATE_NO_WINDOW = 0x08000000 if os.name == "nt" else 0
|
|
|
|
|
|
def is64():
|
|
return struct.calcsize("P") * 8 == 64
|
|
|
|
|
|
def _exe(name="java"):
|
|
return name + (".exe" if os.name == "nt" else "")
|
|
|
|
|
|
def java_major(path):
|
|
"""Версия Java в виде числа (8, 11, 17, 21...), 0 если не определилась."""
|
|
try:
|
|
p = subprocess.run(
|
|
[path, "-version"],
|
|
capture_output=True, text=True, timeout=20,
|
|
creationflags=CREATE_NO_WINDOW,
|
|
)
|
|
text = (p.stderr or "") + (p.stdout or "")
|
|
except Exception:
|
|
return 0
|
|
m = re.search(r'version\s+"(\d+)(?:\.(\d+))?(?:_(\d+))?', text)
|
|
if not m:
|
|
return 0
|
|
major = int(m.group(1))
|
|
if major == 1: # старый формат 1.8.0_382
|
|
return int(m.group(2) or 0)
|
|
return major
|
|
|
|
|
|
def _add(cands, home):
|
|
if not home:
|
|
return
|
|
exe = os.path.join(home, "bin", _exe())
|
|
if os.path.isfile(exe) and exe not in cands:
|
|
cands.append(exe)
|
|
|
|
|
|
def find_javas():
|
|
cands = []
|
|
|
|
# Переменные окружения
|
|
_add(cands, os.environ.get("JAVA_HOME"))
|
|
for k, v in os.environ.items():
|
|
if k.upper().startswith("JAVA_HOME") and v:
|
|
_add(cands, v)
|
|
|
|
# PATH
|
|
w = shutil.which(_exe())
|
|
if w:
|
|
cands.append(w)
|
|
|
|
if os.name == "nt":
|
|
pf = [
|
|
r"C:\Program Files\Java",
|
|
r"C:\Program Files (x86)\Java",
|
|
r"C:\Program Files\Eclipse Adoptium",
|
|
r"C:\Program Files\AdoptOpenJDK",
|
|
r"C:\Program Files\Microsoft",
|
|
r"C:\Program Files\Amazon Corretto",
|
|
r"C:\Program Files\Zulu",
|
|
r"C:\Program Files\BellSoft",
|
|
r"C:\Program Files\GraalVM",
|
|
r"C:\Program Files\JetBrains",
|
|
]
|
|
home_jdks = os.path.join(os.path.expanduser("~"), ".jdks")
|
|
pf.append(home_jdks)
|
|
for root in pf:
|
|
if not os.path.isdir(root):
|
|
continue
|
|
try:
|
|
for name in os.listdir(root):
|
|
_add(cands, os.path.join(root, name))
|
|
except OSError:
|
|
pass
|
|
elif sys.platform == "darwin":
|
|
for root in ("/Library/Java/JavaVirtualMachines",
|
|
os.path.expanduser("~/Library/Java/JavaVirtualMachines")):
|
|
if not os.path.isdir(root):
|
|
continue
|
|
try:
|
|
for name in os.listdir(root):
|
|
_add(cands, os.path.join(root, name, "Contents", "Home"))
|
|
except OSError:
|
|
pass
|
|
else:
|
|
jvm_dir = "/usr/lib/jvm"
|
|
if os.path.isdir(jvm_dir):
|
|
try:
|
|
for name in os.listdir(jvm_dir):
|
|
_add(cands, os.path.join(jvm_dir, name))
|
|
except OSError:
|
|
pass
|
|
|
|
return cands
|
|
|
|
|
|
def pick_java(required=8, preferred="", cache_path=""):
|
|
"""Выбирает Java: (путь, major, подходит_ли).
|
|
|
|
Из подходящих (major >= required) берётся самая старая — максимальная
|
|
совместимость со старыми версиями Minecraft.
|
|
|
|
cache_path — файл кэша с результатами определения версии. Пока файл
|
|
java.exe не менялся, `java -version` повторно не запускается.
|
|
"""
|
|
if preferred and os.path.isfile(preferred):
|
|
major = java_major(preferred)
|
|
return preferred, major, (major == 0 or major >= required)
|
|
|
|
cache = {}
|
|
if cache_path:
|
|
try:
|
|
with open(cache_path, encoding="utf-8") as f:
|
|
cache = json.load(f)
|
|
if not isinstance(cache, dict):
|
|
cache = {}
|
|
except Exception: # noqa: BLE001
|
|
cache = {}
|
|
|
|
changed = False
|
|
cands = find_javas()
|
|
scored = []
|
|
for exe in cands:
|
|
try:
|
|
mtime = int(os.path.getmtime(exe))
|
|
except OSError:
|
|
continue
|
|
ent = cache.get(exe)
|
|
if isinstance(ent, list) and len(ent) == 2 and ent[1] == mtime:
|
|
major = int(ent[0]) # кэш валиден: файл не менялся
|
|
else:
|
|
major = java_major(exe)
|
|
cache[exe] = [major, mtime]
|
|
changed = True
|
|
if major > 0:
|
|
scored.append((exe, major))
|
|
|
|
# выбывшие установки — убрать из кэша
|
|
alive = set(cands)
|
|
for exe in [e for e in cache if e not in alive]:
|
|
del cache[exe]
|
|
changed = True
|
|
|
|
if cache_path and changed:
|
|
try:
|
|
os.makedirs(os.path.dirname(cache_path), exist_ok=True)
|
|
with open(cache_path, "w", encoding="utf-8") as f:
|
|
json.dump(cache, f)
|
|
except OSError:
|
|
pass
|
|
|
|
if not scored:
|
|
return "", 0, False
|
|
|
|
suitable = [s for s in scored if s[1] >= required]
|
|
if suitable:
|
|
suitable.sort(key=lambda s: s[1])
|
|
exe, major = suitable[0]
|
|
return exe, major, True
|
|
# Ничего подходящего — берём самую новую и предупредим
|
|
scored.sort(key=lambda s: s[1])
|
|
exe, major = scored[-1]
|
|
return exe, major, False
|