"""Пути и настройки лаунчера (settings.json рядом с main.py).""" import json import os APP_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SETTINGS_PATH = os.path.join(APP_DIR, "settings.json") DEFAULT_GAME_DIR = os.path.join(APP_DIR, ".minecraft") DEFAULTS = { "username": "Player", "version": "latest-release", "ram": 2048, "auto_ram": False, "java_path": "", "jvm_args": "", "jvm_preset": "", "game_dir": DEFAULT_GAME_DIR, # аккаунты (оффлайн-ники) и активный "accounts": ["Player"], "active_account": "Player", # интерфейс "lang": "ru", "theme": "dark", # "dark" | "light" "accent": "#6fae4a", "ui_scale": 1.0, "scene": "day", # запуск игры "win_w": 0, # 0 = значение игры по умолчанию "win_h": 0, "fullscreen": False, "auto_backup": False, # резервная копия мира перед запуском "autostart": False, # ярлык в автозагрузке Windows "minimize_to_tray_on_launch": False, # прятать окно при запуске игры # прокси: {"type": "http"|"socks5", "host", "port", "user", "pass"} "proxy": {"enabled": False, "type": "http", "host": "", "port": 1080, "user": "", "pass": ""}, # зеркала скачивания (пустые списки = официальные серверы Mojang) "asset_mirrors": [], "lib_mirrors": [], # ключ CurseForge API (необязателен: есть зеркало без ключа) "cf_api_key": "", # третья волна "fav_versions": [], # закреплённые версии (id) "profiles": [], # профили запуска (см. ui._open_profiles) "active_profile": "", # имя применённого профиля ("" — без профиля) "world_backups": 5, # сколько последних копий мира хранить # мастер первоначальной настройки (False — показать при первом запуске; # апгрейд со старой версии считается настроенным, см. load_settings) "onboarding_done": False, } def load_settings(): s = dict(DEFAULTS) try: with open(SETTINGS_PATH, encoding="utf-8") as f: data = json.load(f) except Exception: data = None if isinstance(data, dict): s.update(data) if "onboarding_done" not in data: # апгрейд со старой версии: мастер первого запуска не показываем s["onboarding_done"] = True _migrate(s) return s def _migrate(s): """Приводит старые настройки к новой структуре.""" nick = (s.get("username") or "Player").strip() or "Player" accounts = s.get("accounts") if not isinstance(accounts, list) or not accounts: accounts = [nick] accounts = [str(a).strip() for a in accounts if str(a).strip()] if nick not in accounts: accounts.insert(0, nick) s["accounts"] = accounts active = s.get("active_account") if not isinstance(active, str) or active not in accounts: s["active_account"] = nick if nick in accounts else accounts[0] s["username"] = s["active_account"] for key in ("asset_mirrors", "lib_mirrors"): v = s.get(key) if not isinstance(v, list): s[key] = [] if not isinstance(s.get("accent"), str) or not s["accent"].startswith("#"): s["accent"] = DEFAULTS["accent"] if not isinstance(s.get("cf_api_key"), str): s["cf_api_key"] = "" if s.get("theme") not in ("dark", "light"): s["theme"] = "dark" proxy = s.get("proxy") if not isinstance(proxy, dict): proxy = dict(DEFAULTS["proxy"]) for k, dv in DEFAULTS["proxy"].items(): if k not in proxy: proxy[k] = dv s["proxy"] = proxy if s.get("scene") not in ("day", "night", "nether", "cave", "random"): s["scene"] = "day" if s.get("ui_scale") not in (1.0, 1.25, 1.5): try: sc = float(s.get("ui_scale")) s["ui_scale"] = sc if sc in (1.0, 1.25, 1.5) else 1.0 except (TypeError, ValueError): s["ui_scale"] = 1.0 for k in ("win_w", "win_h"): try: s[k] = max(0, int(s.get(k, 0))) except (TypeError, ValueError): s[k] = 0 s["fullscreen"] = bool(s.get("fullscreen")) s["auto_backup"] = bool(s.get("auto_backup")) s["autostart"] = bool(s.get("autostart")) s["minimize_to_tray_on_launch"] = bool( s.get("minimize_to_tray_on_launch")) fav = s.get("fav_versions") if isinstance(fav, list): seen_f, out_f = set(), [] for v in fav[:200]: v = str(v).strip() if v and v not in seen_f: seen_f.add(v) out_f.append(v) s["fav_versions"] = out_f else: s["fav_versions"] = [] profs, names = [], set() if isinstance(s.get("profiles"), list): for p in s["profiles"][:100]: name = str((p or {}).get("name", "")).strip() if isinstance( p, dict) else "" if name and name not in names: names.add(name) profs.append(p) s["profiles"] = profs ap = s.get("active_profile") s["active_profile"] = ap if isinstance(ap, str) and ap in names else "" try: s["world_backups"] = min(50, max(1, int(s.get("world_backups", 5)))) except (TypeError, ValueError): s["world_backups"] = 5 def save_settings(s): try: with open(SETTINGS_PATH, "w", encoding="utf-8") as f: json.dump(s, f, indent=2, ensure_ascii=False) except Exception: pass