Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Лаунчер читов майнкрафт

Начинающий
Начинающий
Статус
Оффлайн
Регистрация
12 Май 2025
Сообщения
144
Реакции
1
Я делал лаунчер читов для майнкрафта для себя, и как только попытался добавить читы 1.21 и сделать для них адекватную логику запуска, у меня всё сломалось :(
теперь не запускаются не 1.16.5 не 1.21 читы.
Может кто помочь?
на пайтоне
 
Приложи паспорт кота, мед карту с прививками для таракана, ИНН стола на кухне. А также, по возможности, код лаунчера, сделанного через яндекс ИИ (no ad)
 
Приложи паспорт кота, мед карту с прививками для таракана, ИНН стола на кухне. А также, по возможности, код лаунчера, сделанного через яндекс ИИ (no ad)
import os
import sys
import threading
import time
import zipfile
import shutil
import subprocess
import json
import requests
import locale
import ctypes
import psutil

# ================= ССЫЛКИ НА АРХИВЫ (ОБЪЯВЛЕНЫ ПЕРВЫМИ) =================
URL_BASE_MC = "
Пожалуйста, авторизуйтесь для просмотра ссылки.
"
URL_FABRIC_1214 = "
Пожалуйста, авторизуйтесь для просмотра ссылки.
"
URL_FABRIC_1218 = "
Пожалуйста, авторизуйтесь для просмотра ссылки.
"

F_API_1_21_4 = "
Пожалуйста, авторизуйтесь для просмотра ссылки.
"
F_API_1_21_8 = "
Пожалуйста, авторизуйтесь для просмотра ссылки.
"

# ================= СИСТЕМА =================
if sys.platform == "win32":
ctypes.windll.kernel32.SetConsoleOutputCP(65001)
try: locale.setlocale(locale.LC_ALL, 'ru_RU.UTF-8')
except: locale.setlocale(locale.LC_ALL, '')

def install_libs():
for lib in ["minecraft_launcher_lib", "pywebview"]:
try: __import__(lib.replace("-", "_"))
except ImportError: subprocess.check_call([sys.executable, "-m", "pip", "install", lib, "--quiet"])

install_libs()
import webview
import minecraft_launcher_lib

# ================= КЛИЕНТЫ =================
CHECKS = {
"soul": {"name": "SoulDLC", "tag": "OLD", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "soul", "mode": "clone", "mc": "1.16.5"},
"venus": {"name": "VenusFree", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "VenusFree", "mode": "clone", "mc": "1.16.5"},
"nightdlc113": {"name": "NightDLC 1.1.3", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "nightdlc113", "mode": "clone", "mc": "1.16.5"},
"nightdlc": {"name": "NightDLC 1.1.2", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "night", "mode": "clone", "mc": "1.16.5"},
"dimasik": {"name": "Dimasik", "tag": "OLD", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "dimasik", "mode": "clone", "mc": "1.16.5"},
"neverlose": {"name": "NeverLose", "tag": "OLD", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "NeverLose", "mode": "clone", "mc": "1.16.5"},

"javelin": {"name": "Javelin", "tag": "1.21.8", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "Javelin", "mode": "fabric", "mc": "1.21.8", "api": F_API_1_21_8},
"rockstar": {"name": "RockStar", "tag": "1.21.4", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "RockStar", "mode": "fabric", "mc": "1.21.4", "api": F_API_1_21_4},
"stellar": {"name": "Stellar", "tag": "1.21.4", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "Stellar", "mode": "fabric", "mc": "1.21.4", "api": F_API_1_21_4},
"wild": {"name": "Wild", "tag": "1.21.4", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "Wild", "mode": "fabric", "mc": "1.21.4", "api": F_API_1_21_4},
}

SETTINGS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "settings.json")

# ================= HTML =================
HTML = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
@import url('
Пожалуйста, авторизуйтесь для просмотра ссылки.

:root { --bg: #0b0b0b; --panel: #141414; --border: #2a2a2a; --accent: #ffffff; --text: #e0e0e0; }
body.light { --bg: #f5f5f5; --panel: #ffffff; --border: #ddd; --text: #111; }
body { background: var(--bg); color: var(--text); font-family: 'Inter'; margin: 0; padding: 0; height: 100vh; display: flex; flex-direction: column; overflow: hidden; user-select: none; }
.head { height: 45px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; padding: 0 16px; -webkit-app-region: drag; background: var(--panel); }
.head-btns { display: flex; align-items: center; gap: 8px; -webkit-app-region: no-drag; }
.win-btn { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #666; transition: 0.2s; border-radius: 6px; }
.win-btn:hover { color: var(--accent); background: var(--border); }
.win-btn svg { width: 22px; height: 22px; fill: currentColor; }
.body { flex: 1; padding: 20px; display: flex; flex-direction: column; gap: 12px; -webkit-app-region: no-drag; box-sizing: border-box; }
.lbl { font-size: 11px; font-weight: 800; color: #666; text-transform: uppercase; margin-bottom: 2px; }
.custom-select { position: relative; width: 100%; box-sizing: border-box; }
.select-trigger { padding: 12px; border-radius: 8px; background: var(--bg); border: 1px solid var(--border); color: var(--text); font-family: 'JetBrains Mono'; font-size: 12px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; box-sizing: border-box; }
.select-options { position: absolute; top: 100%; left: 0; right: 0; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; margin-top: 5px; max-height: 180px; overflow-y: auto; display: none; z-index: 100; box-shadow: 0 5px 15px rgba(0,0,0,0.5); }
.select-options.show { display: block; }
.option { padding: 10px 12px; font-family: 'JetBrains Mono'; font-size: 12px; cursor: pointer; display: flex; align-items: center; }
.option:hover { background: var(--border); }
.option b, .select-trigger b { color: var(--accent) !important; font-weight: 900 !important; margin: 0 6px; text-transform: uppercase; }
.v-tag { color: #666; font-size: 10px; margin-left: auto; }
input { width: 100%; padding: 12px; border-radius: 8px; background: var(--bg); border: 1px solid var(--border); color: var(--text); font-family: 'JetBrains Mono'; font-size: 12px; outline: none; box-sizing: border-box; }
.ram-box { padding: 5px 0; -webkit-app-region: no-drag !important; }
input[type=range] { -webkit-appearance: none; width: 100%; height: 2px; background: var(--border); outline: none; -webkit-app-region: no-drag !important; }
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; background: var(--accent); border-radius: 50%; cursor: pointer; border: 4px solid var(--panel); -webkit-app-region: no-drag !important; }
.theme-row { display: flex; justify-content: space-between; align-items: center; -webkit-app-region: no-drag; }
.palette { display: flex; gap: 8px; align-items: center; }
.dot { width: 18px; height: 18px; border-radius: 50%; cursor: pointer; border: 2px solid transparent; }
.dot.active { border-color: var(--text); transform: scale(1.1); }
.color-picker-btn { width: 18px; height: 18px; border-radius: 50%; background: linear-gradient(45deg,red,orange,yellow,green,cyan,blue,violet); cursor: pointer; border: 1px solid var(--border); overflow: hidden; position: relative; }
.color-picker-btn input { position: absolute; opacity: 0; width: 100%; height: 100%; cursor: pointer; top:0; left:0; }
.mode-sw { font-size: 10px; font-weight: 800; cursor: pointer; padding: 6px 12px; background: var(--border); border-radius: 6px; }
#launch { width: 100%; padding: 16px; border: none; border-radius: 10px; background: var(--accent); color: #000; font-weight: 800; font-size: 14px; cursor: pointer; margin-top: auto; -webkit-app-region: no-drag; }
.bar-con { height: 4px; background: var(--border); margin-top: 15px; border-radius: 2px; overflow: hidden; }
.bar { height: 100%; width: 0%; background: var(--accent); transition: 0.3s; }
.term { width: 100%; height: 100px; background: #080808; border: 1px solid var(--border); border-radius: 8px; padding: 10px; font-family: 'JetBrains Mono'; font-size: 10px; color: #bbb; overflow-y: auto; display: none; margin-top: 10px; box-sizing: border-box; word-break: break-all; white-space: pre-wrap; }
.term.show { display: block; }
</style>
</head>
<body onclick="closeAll()">
<div class="head">
<div style="font-weight:800; font-size:13px;">MYST LAUNCHER v7</div>
<div class="head-btns">
<div class="win-btn" onclick="pywebview.api.open_url('
Пожалуйста, авторизуйтесь для просмотра ссылки.
')"><svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69a.2.2 0 00-.05-.18c-.06-.05-.14-.03-.21-.02-.09.02-1.49.95-4.22 2.79-.4.27-.76.41-1.08.4-.36-.01-1.04-.2-1.55-.37-.63-.2-1.12-.31-1.08-.66.02-.18.27-.36.74-.55 2.92-1.27 4.86-2.11 5.83-2.51 2.78-1.16 3.35-1.36 3.73-1.36.08 0 .27.02.39.12.1.08.13.19.14.27-.01.06.01.24 0 .38z"/></svg></div>
<div class="win-btn" onclick="pywebview.api.open_url('
Пожалуйста, авторизуйтесь для просмотра ссылки.
')"><svg viewBox="0 0 24 24"><path fill="currentColor" d="M19.73 4.87a18.2 18.2 0 0 0-4.6-1.44c-.21.4-.4.8-.58 1.21a17.1 17.1 0 0 0-5.1 0 13.5 13.5 0 0 0-.58-1.21 18.2 18.2 0 0 0-4.6 1.44A18.8 18.8 0 0 0 .12 18.05c2.42 1.78 4.76 2.86 7.07 3.58.54-.74 1.01-1.53 1.41-2.37a11.6 11.6 0 0 1-2.18-1.1c.14-.11.29-.22.43-.34 4.66 2.13 9.7 2.13 14.3 0 .14.11.29.23.43.34-.68.44-1.41.81-2.18 1.1.4.84 1.01 1.54 1.41 2.37 2.3-.71 4.65-1.8 7.07-3.58a18.8 18.8 0 0 0-3.66-13.18zM8.02 15.33c-1.18 0-2.16-1.08-2.16-2.42s.96-2.42 2.16-2.42c1.21 0 2.18 1.1 2.16 2.42 0 1.33-.96 2.42-2.16 2.42zm7.97 0c-1.18 0-2.16-1.08-2.16-2.42s.96-2.42 2.16-2.42c1.21 0 2.18 1.1 2.16 2.42 0 1.33-.95 2.42-2.16 2.42z"/></svg></div>
<div style="width:10px"></div>
<div class="win-btn" onclick="pywebview.api.min()">_</div>
<div class="win-btn" onclick="pywebview.api.close()">✕</div>
</div>
</div>
<div class="body">
<div><div class="lbl">Nickname</div><input id="nick" type="text" placeholder="Username" onchange="save()"></div>
<div><div class="lbl">Client Version</div><div class="custom-select" id="custom-ver"><div class="select-trigger" onclick="toggleSelect(event)"><span id="selected-text">Select Client</span><span>▼</span></div><div class="select-options" id="options-list"></div></div></div>
<div class="ram-box"><div class="lbl">RAM ALLOCATION <span id="ram-val" style="color:var(--accent); font-weight:900;">4 GB</span></div><input type="range" id="ram" min="1" max="16" step="1" oninput="updateRam(this.value)" onchange="save()"></div>
<div><div class="lbl">Install Path</div><div style="display:flex; gap:8px"><input id="dl_path" type="text" readonly><button style="padding:0 15px; border-radius:8px; border:1px solid var(--border); background:var(--panel); color:var(--text); cursor:pointer;" onclick="browse_dir()">...</button></div></div>
<div><div class="lbl">Java Path</div><div style="display:flex; gap:8px"><input id="java" type="text" readonly><button style="padding:0 15px; border-radius:8px; border:1px solid var(--border); background:var(--panel); color:var(--text); cursor:pointer;" onclick="browse_java()">...</button></div></div>
<div class="theme-row"><div class="palette"><div class="dot active" style="background:#ffffff" onclick="clr('#ffffff',this)"></div><div class="dot" style="background:#00ff9d" onclick="clr('#00ff9d',this)"></div><div class="dot" style="background:#ff4757" onclick="clr('#ff4757',this)"></div><div class="dot" style="background:#1e90ff" onclick="clr('#1e90ff',this)"></div><div class="dot" style="background:#bf55ec" onclick="clr('#bf55ec',this)"></div><div class="color-picker-btn"><input type="color" oninput="clr(this.value, null)"></div></div><div class="mode-sw" onclick="toggleTheme()">THEME</div></div>
<div style="margin-top:auto"><button id="launch" onclick="go()">LAUNCH CLIENT</button><div class="bar-con"><div class="bar" id="bar"></div></div><div style="display:flex; justify-content:space-between; font-size:10px; color:#666; margin-top:5px; font-family:'JetBrains Mono';"><span id="st-txt">IDLE</span><span id="st-pct">0%</span></div></div>
<div class="term" id="term"></div>
</div>
<script>
let currentVer = "";
function toggleSelect(e) { e.stopPropagation(); document.getElementById('options-list').classList.toggle('show'); }
function closeAll() { document.getElementById('options-list').classList.remove('show'); }
function selectOpt(id, name, tag, mc) {
currentVer = id; let tagStr = tag ? ` <b style="color:var(--accent)">${tag}</b>` : "";
document.getElementById('selected-text').innerHTML = name + tagStr + ` <span class="v-tag">${mc}</span>`;
closeAll(); save();
}
function updateRam(v) { document.getElementById('ram-val').innerText = v + ' GB'; }
function toggleTheme() { document.body.classList.toggle('light'); save(); }
function clr(c, el) {
document.documentElement.style.setProperty('--accent', c);
document.querySelectorAll('.dot').forEach(d => d.classList.remove('active'));
if(el) el.classList.add('active'); save();
}
function browse_java() { pywebview.api.browse_java().then(p => { if(p) { document.getElementById('java').value = p; save(); }}); }
function browse_dir() { pywebview.api.browse_dir().then(p => { if(p) { document.getElementById('dl_path').value = p; save(); }}); }
function save() {
pywebview.api.save_cfg({nick: document.getElementById('nick').value, ver: currentVer, ram: document.getElementById('ram').value, java: document.getElementById('java').value, dl_path: document.getElementById('dl_path').value, theme: document.body.classList.contains('light')?'light':'dark', color: document.documentElement.style.getPropertyValue('--accent')});
}
function restore(d, versions, maxRam) {
const list = document.getElementById('options-list');
versions.forEach(v => {
const div = document.createElement('div'); div.className = 'option';
div.onclick = () => selectOpt(v.id, v.name, v.tag, v.mc);
div.innerHTML = `<span>${v.name}</span>${v.tag ? `<b style="color:var(--accent); font-weight:900;">${v.tag}</b>` : ""}<span class="v-tag">${v.mc}</span>`;
list.appendChild(div); if(d.ver === v.id) selectOpt(v.id, v.name, v.tag, v.mc);
});
document.getElementById('ram').max = maxRam;
if(d.nick) document.getElementById('nick').value = d.nick;
if(d.ram) { document.getElementById('ram').value = d.ram; updateRam(d.ram); }
if(d.java) document.getElementById('java').value = d.java;
if(d.dl_path) document.getElementById('dl_path').value = d.dl_path;
if(d.theme === 'light') document.body.classList.add('light');
if(d.color) clr(d.color, Array.from(document.querySelectorAll('.dot')).find(el => el.style.backgroundColor.includes(d.color)));
}
function go() { document.getElementById('launch').disabled = true; document.getElementById('term').classList.add('show'); document.getElementById('term').innerHTML = ''; pywebview.api.start(); }
function log(m, t) { const c = document.getElementById('term'); const d = document.createElement('div'); if(t==='err') d.style.color='#f55'; else if(t==='ok') d.style.color='#5f5'; d.innerText = '> ' + m; c.appendChild(d); c.scrollTop = c.scrollHeight; }
function prog(v, t) { document.getElementById('bar').style.width = v + '%'; document.getElementById('st-pct').innerText = Math.round(v) + '%'; if(t) document.getElementById('st-txt').innerText = t.toUpperCase(); }
function done() { document.getElementById('launch').disabled = false; }
</script>
</body>
</html>
"""

# ================= ЛОГИКА =================
class Api:
def init(self):
max_ram = int(psutil.virtual_memory().total / (1024**3))
self.cfg = self.load_settings()
if not self.cfg.get('java'): self.cfg['java'] = self.find_java()
if not self.cfg.get('dl_path'): self.cfg['dl_path'] = os.path.join(os.path.expanduser("~"), "Desktop")
v_list = [{"id": k, "name": v['name'], "tag": v.get('tag', ''), "mc": v['mc']} for k, v in CHECKS.items()]
window.evaluate_js(f'restore({json.dumps(self.cfg)}, {json.dumps(v_list)}, {max_ram})')

def load_settings(self):
if os.path.exists(SETTINGS_FILE):
try: return json.load(open(SETTINGS_FILE, 'r'))
except: return {}
return {}

def find_java(self):
for v in ["21", "17", "8"]:
p = f"C:\\Program Files\\Java\\jdk-{v}\\bin\\javaw.exe"
if os.path.exists(p): return p
return shutil.which("javaw") or ""

def save_cfg(self, d): self.cfg = d; open(SETTINGS_FILE, 'w').write(json.dumps(d))
def min(self): window.minimize()
def close(self): window.destroy()
def open_url(self, u): import webbrowser; webbrowser.open(u)
def browse_java(self):
res = window.create_file_dialog(webview.OPEN_DIALOG, file_types=('Executables (*.exe)',))
return res[0] if res else None
def browse_dir(self):
res = window.create_file_dialog(webview.FOLDER_DIALOG)
return res[0] if res else None

def log(self, m, t=''): window.evaluate_js(f'log({json.dumps(str(m))}, "{t}")')
def prog(self, v, t=None): window.evaluate_js(f'prog({v}, {json.dumps(t)})')

def _dl_v1(self, url, path, p_val, p_txt):
self.log(f"Downloading {p_txt}...")
self.prog(p_val, p_txt)
try:
r = requests.get(url, timeout=300)
if r.status_code == 200:
with open(path, 'wb') as f: f.write(r.content)
return True
return False
except Exception as e:
self.log(f"Error: {e}", "err"); return False

def _extract_v1(self, zip_path, target_dir, internal_folder=None):
"""Простая распаковка с опциональным переносом из внутренней папки."""
self.log(f"Extracting {os.path.basename(zip_path)}...")
temp_dir = os.path.join(target_dir, "_tmp_ext")
if os.path.exists(temp_dir): shutil.rmtree(temp_dir)
os.makedirs(temp_dir)

with zipfile.ZipFile(zip_path, 'r') as z:
z.extractall(temp_dir)

source = temp_dir
if internal_folder:
potential = os.path.join(temp_dir, internal_folder)
if os.path.exists(potential): source = potential

for item in os.listdir(source):
s, d = os.path.join(source, item), os.path.join(target_dir, item)
if os.path.isdir(s):
if os.path.exists(d): shutil.rmtree(d)
shutil.move(s, d)
else: shutil.move(s, d)
shutil.rmtree(temp_dir)

def start(self): threading.Thread(target=self._run, daemon=True).start()

def _run(self):
try:
info = CHECKS[self.cfg['ver']]
java, ram, nick = self.cfg['java'], self.cfg.get('ram', 4), self.cfg.get('nick', 'Player')
myst_dir = os.path.join(self.cfg['dl_path'], "MystLauncher")
os.makedirs(myst_dir, exist_ok=True)

if info['mode'] == 'fabric':
self._run_fabric(info, java, ram, nick, myst_dir)
else:
self._run_standard(info, java, ram, nick, myst_dir)
except Exception as e:
self.log(f"Launcher Error: {e}", "err")
window.evaluate_js('done()')

def _run_fabric(self, info, java, ram, nick, myst_dir):
# Папка чита: MystLauncher/Stellar/
cheat_dir = os.path.join(myst_dir, info['folder'])
os.makedirs(cheat_dir, exist_ok=True)
mc_dir = os.path.join(cheat_dir, ".minecraft")
os.makedirs(mc_dir, exist_ok=True)

# 1. Твоя база .minecraft.zip
if not os.path.exists(os.path.join(mc_dir, "libraries")):
zip_p = os.path.join(cheat_dir, "base.zip")
if self._dl_v1(URL_BASE_MC, zip_p, 10, "Base Minecraft"):
self._extract_v1(zip_p, cheat_dir, ".minecraft")
os.remove(zip_p)

# 2. Твой Fabric Zip
if not os.path.exists(os.path.join(mc_dir, "versions")):
f_url = URL_FABRIC_1218 if "1.21.8" in info['mc'] else URL_FABRIC_1214
f_zip = os.path.join(cheat_dir, "fabric.zip")
f_folder = f"Fabric {info['mc']}"
if self._dl_v1(f_url, f_zip, 40, "Fabric Components"):
self._extract_v1(f_zip, mc_dir, f_folder)
os.remove(f_zip)

# 3. Мод и API
mods_dir = os.path.join(mc_dir, "mods")
os.makedirs(mods_dir, exist_ok=True)
self._dl_v1(info['api'], os.path.join(mods_dir, "fabric-api.jar"), 70, "API")
self._dl_v1(info['url'], os.path.join(mods_dir, "client.jar"), 85, "Cheat Mod")

v_folder = os.path.join(mc_dir, "versions")
v_id = next(d for d in os.listdir(v_folder) if "fabric" in d.lower())
self._launch(v_id, java, ram, nick, mc_dir)

def _run_standard(self, info, java, ram, nick, myst_dir):
# 1.16.5 в общую папку MystLauncher/
mc_dir = os.path.join(myst_dir, ".minecraft")
os.makedirs(mc_dir, exist_ok=True)

if not os.path.exists(os.path.join(mc_dir, "libraries")):
zip_p = os.path.join(myst_dir, "base_legacy.zip")
if self._dl_v1(URL_BASE_MC, zip_p, 10, "Base Assets"):
self._extract_v1(zip_p, myst_dir, ".minecraft")
os.remove(zip_p)

target = os.path.join(mc_dir, "versions", info['folder'])
jar = os.path.join(target, f"{info['folder']}.jar")
if not os.path.exists(jar):
zip_p = os.path.join(myst_dir, "cheat_116.zip")
if self._dl_v1(info['url'], zip_p, 60, "Cheat Files"):
os.makedirs(target, exist_ok=True)
with zipfile.ZipFile(zip_p, 'r') as z: z.extractall(target)
os.remove(zip_p)
for r, d, files in os.walk(target):
for f in files:
if f.endswith(".jar"): shutil.move(os.path.join(r, f), jar); break

self._launch(info['folder'], java, ram, nick, mc_dir)

def _launch(self, v_id, java, ram, nick, g_dir):
self.prog(95, "Ready")
# Поиск библиотек в правильной папке
opts = {
"username": nick, "uuid": "0", "token": "0", "executablePath": java,
"gameDirectory": g_dir,
"jvmArguments": [f"-Xmx{ram}G", "-noverify", "-Dfile.encoding=UTF-8"]
}
try:
# Важно: minecraft_launcher_lib должна знать, где искать библиотеки
cmd = minecraft_launcher_lib.command.get_minecraft_command(v_id, g_dir, opts)

# Фикс для jopt-simple (для 1.16.5)
lib_j = os.path.join(g_dir, "libraries", "net", "sf", "jopt-simple", "jopt-simple", "5.0.4", "jopt-simple-5.0.4.jar")
if os.path.exists(lib_j):
cp_idx = cmd.index("-cp") + 1
if lib_j not in cmd[cp_idx]: cmd[cp_idx] += os.pathsep + lib_j

self.log(f"Launching {v_id}...", "ok"); self.prog(100, "Launched")
subprocess.Popen(cmd, cwd=g_dir)
except Exception as e:
self.log(f"Launch Error: {e}", "err")

if __name__ == '__main__':
api = Api()
window = webview.create_window("Myst Launcher v7", html=HTML, width=420, height=720, frameless=True, js_api=api)
webview.start(api.init)
 
import os
import sys
import threading
import time
import zipfile
import shutil
import subprocess
import json
import requests
import locale
import ctypes
import psutil

# ================= ССЫЛКИ НА АРХИВЫ (ОБЪЯВЛЕНЫ ПЕРВЫМИ) =================
URL_BASE_MC = "
Пожалуйста, авторизуйтесь для просмотра ссылки.
"
URL_FABRIC_1214 = "
Пожалуйста, авторизуйтесь для просмотра ссылки.
"
URL_FABRIC_1218 = "
Пожалуйста, авторизуйтесь для просмотра ссылки.
"

F_API_1_21_4 = "
Пожалуйста, авторизуйтесь для просмотра ссылки.
"
F_API_1_21_8 = "
Пожалуйста, авторизуйтесь для просмотра ссылки.
"

# ================= СИСТЕМА =================
if sys.platform == "win32":
ctypes.windll.kernel32.SetConsoleOutputCP(65001)
try: locale.setlocale(locale.LC_ALL, 'ru_RU.UTF-8')
except: locale.setlocale(locale.LC_ALL, '')

def install_libs():
for lib in ["minecraft_launcher_lib", "pywebview"]:
try: __import__(lib.replace("-", "_"))
except ImportError: subprocess.check_call([sys.executable, "-m", "pip", "install", lib, "--quiet"])

install_libs()
import webview
import minecraft_launcher_lib

# ================= КЛИЕНТЫ =================
CHECKS = {
"soul": {"name": "SoulDLC", "tag": "OLD", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "soul", "mode": "clone", "mc": "1.16.5"},
"venus": {"name": "VenusFree", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "VenusFree", "mode": "clone", "mc": "1.16.5"},
"nightdlc113": {"name": "NightDLC 1.1.3", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "nightdlc113", "mode": "clone", "mc": "1.16.5"},
"nightdlc": {"name": "NightDLC 1.1.2", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "night", "mode": "clone", "mc": "1.16.5"},
"dimasik": {"name": "Dimasik", "tag": "OLD", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "dimasik", "mode": "clone", "mc": "1.16.5"},
"neverlose": {"name": "NeverLose", "tag": "OLD", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "NeverLose", "mode": "clone", "mc": "1.16.5"},

"javelin": {"name": "Javelin", "tag": "1.21.8", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "Javelin", "mode": "fabric", "mc": "1.21.8", "api": F_API_1_21_8},
"rockstar": {"name": "RockStar", "tag": "1.21.4", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "RockStar", "mode": "fabric", "mc": "1.21.4", "api": F_API_1_21_4},
"stellar": {"name": "Stellar", "tag": "1.21.4", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "Stellar", "mode": "fabric", "mc": "1.21.4", "api": F_API_1_21_4},
"wild": {"name": "Wild", "tag": "1.21.4", "url": "
Пожалуйста, авторизуйтесь для просмотра ссылки.
", "folder": "Wild", "mode": "fabric", "mc": "1.21.4", "api": F_API_1_21_4},
}

SETTINGS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "settings.json")

# ================= HTML =================
HTML = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
@import url('
Пожалуйста, авторизуйтесь для просмотра ссылки.

:root { --bg: #0b0b0b; --panel: #141414; --border: #2a2a2a; --accent: #ffffff; --text: #e0e0e0; }
body.light { --bg: #f5f5f5; --panel: #ffffff; --border: #ddd; --text: #111; }
body { background: var(--bg); color: var(--text); font-family: 'Inter'; margin: 0; padding: 0; height: 100vh; display: flex; flex-direction: column; overflow: hidden; user-select: none; }
.head { height: 45px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; padding: 0 16px; -webkit-app-region: drag; background: var(--panel); }
.head-btns { display: flex; align-items: center; gap: 8px; -webkit-app-region: no-drag; }
.win-btn { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #666; transition: 0.2s; border-radius: 6px; }
.win-btn:hover { color: var(--accent); background: var(--border); }
.win-btn svg { width: 22px; height: 22px; fill: currentColor; }
.body { flex: 1; padding: 20px; display: flex; flex-direction: column; gap: 12px; -webkit-app-region: no-drag; box-sizing: border-box; }
.lbl { font-size: 11px; font-weight: 800; color: #666; text-transform: uppercase; margin-bottom: 2px; }
.custom-select { position: relative; width: 100%; box-sizing: border-box; }
.select-trigger { padding: 12px; border-radius: 8px; background: var(--bg); border: 1px solid var(--border); color: var(--text); font-family: 'JetBrains Mono'; font-size: 12px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; box-sizing: border-box; }
.select-options { position: absolute; top: 100%; left: 0; right: 0; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; margin-top: 5px; max-height: 180px; overflow-y: auto; display: none; z-index: 100; box-shadow: 0 5px 15px rgba(0,0,0,0.5); }
.select-options.show { display: block; }
.option { padding: 10px 12px; font-family: 'JetBrains Mono'; font-size: 12px; cursor: pointer; display: flex; align-items: center; }
.option:hover { background: var(--border); }
.option b, .select-trigger b { color: var(--accent) !important; font-weight: 900 !important; margin: 0 6px; text-transform: uppercase; }
.v-tag { color: #666; font-size: 10px; margin-left: auto; }
input { width: 100%; padding: 12px; border-radius: 8px; background: var(--bg); border: 1px solid var(--border); color: var(--text); font-family: 'JetBrains Mono'; font-size: 12px; outline: none; box-sizing: border-box; }
.ram-box { padding: 5px 0; -webkit-app-region: no-drag !important; }
input[type=range] { -webkit-appearance: none; width: 100%; height: 2px; background: var(--border); outline: none; -webkit-app-region: no-drag !important; }
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; background: var(--accent); border-radius: 50%; cursor: pointer; border: 4px solid var(--panel); -webkit-app-region: no-drag !important; }
.theme-row { display: flex; justify-content: space-between; align-items: center; -webkit-app-region: no-drag; }
.palette { display: flex; gap: 8px; align-items: center; }
.dot { width: 18px; height: 18px; border-radius: 50%; cursor: pointer; border: 2px solid transparent; }
.dot.active { border-color: var(--text); transform: scale(1.1); }
.color-picker-btn { width: 18px; height: 18px; border-radius: 50%; background: linear-gradient(45deg,red,orange,yellow,green,cyan,blue,violet); cursor: pointer; border: 1px solid var(--border); overflow: hidden; position: relative; }
.color-picker-btn input { position: absolute; opacity: 0; width: 100%; height: 100%; cursor: pointer; top:0; left:0; }
.mode-sw { font-size: 10px; font-weight: 800; cursor: pointer; padding: 6px 12px; background: var(--border); border-radius: 6px; }
#launch { width: 100%; padding: 16px; border: none; border-radius: 10px; background: var(--accent); color: #000; font-weight: 800; font-size: 14px; cursor: pointer; margin-top: auto; -webkit-app-region: no-drag; }
.bar-con { height: 4px; background: var(--border); margin-top: 15px; border-radius: 2px; overflow: hidden; }
.bar { height: 100%; width: 0%; background: var(--accent); transition: 0.3s; }
.term { width: 100%; height: 100px; background: #080808; border: 1px solid var(--border); border-radius: 8px; padding: 10px; font-family: 'JetBrains Mono'; font-size: 10px; color: #bbb; overflow-y: auto; display: none; margin-top: 10px; box-sizing: border-box; word-break: break-all; white-space: pre-wrap; }
.term.show { display: block; }
</style>
</head>
<body onclick="closeAll()">
<div class="head">
<div style="font-weight:800; font-size:13px;">MYST LAUNCHER v7</div>
<div class="head-btns">
<div class="win-btn" onclick="pywebview.api.open_url('
Пожалуйста, авторизуйтесь для просмотра ссылки.
')"><svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69a.2.2 0 00-.05-.18c-.06-.05-.14-.03-.21-.02-.09.02-1.49.95-4.22 2.79-.4.27-.76.41-1.08.4-.36-.01-1.04-.2-1.55-.37-.63-.2-1.12-.31-1.08-.66.02-.18.27-.36.74-.55 2.92-1.27 4.86-2.11 5.83-2.51 2.78-1.16 3.35-1.36 3.73-1.36.08 0 .27.02.39.12.1.08.13.19.14.27-.01.06.01.24 0 .38z"/></svg></div>
<div class="win-btn" onclick="pywebview.api.open_url('
Пожалуйста, авторизуйтесь для просмотра ссылки.
')"><svg viewBox="0 0 24 24"><path fill="currentColor" d="M19.73 4.87a18.2 18.2 0 0 0-4.6-1.44c-.21.4-.4.8-.58 1.21a17.1 17.1 0 0 0-5.1 0 13.5 13.5 0 0 0-.58-1.21 18.2 18.2 0 0 0-4.6 1.44A18.8 18.8 0 0 0 .12 18.05c2.42 1.78 4.76 2.86 7.07 3.58.54-.74 1.01-1.53 1.41-2.37a11.6 11.6 0 0 1-2.18-1.1c.14-.11.29-.22.43-.34 4.66 2.13 9.7 2.13 14.3 0 .14.11.29.23.43.34-.68.44-1.41.81-2.18 1.1.4.84 1.01 1.54 1.41 2.37 2.3-.71 4.65-1.8 7.07-3.58a18.8 18.8 0 0 0-3.66-13.18zM8.02 15.33c-1.18 0-2.16-1.08-2.16-2.42s.96-2.42 2.16-2.42c1.21 0 2.18 1.1 2.16 2.42 0 1.33-.96 2.42-2.16 2.42zm7.97 0c-1.18 0-2.16-1.08-2.16-2.42s.96-2.42 2.16-2.42c1.21 0 2.18 1.1 2.16 2.42 0 1.33-.95 2.42-2.16 2.42z"/></svg></div>
<div style="width:10px"></div>
<div class="win-btn" onclick="pywebview.api.min()">_</div>
<div class="win-btn" onclick="pywebview.api.close()">✕</div>
</div>
</div>
<div class="body">
<div><div class="lbl">Nickname</div><input id="nick" type="text" placeholder="Username" onchange="save()"></div>
<div><div class="lbl">Client Version</div><div class="custom-select" id="custom-ver"><div class="select-trigger" onclick="toggleSelect(event)"><span id="selected-text">Select Client</span><span>▼</span></div><div class="select-options" id="options-list"></div></div></div>
<div class="ram-box"><div class="lbl">RAM ALLOCATION <span id="ram-val" style="color:var(--accent); font-weight:900;">4 GB</span></div><input type="range" id="ram" min="1" max="16" step="1" oninput="updateRam(this.value)" onchange="save()"></div>
<div><div class="lbl">Install Path</div><div style="display:flex; gap:8px"><input id="dl_path" type="text" readonly><button style="padding:0 15px; border-radius:8px; border:1px solid var(--border); background:var(--panel); color:var(--text); cursor:pointer;" onclick="browse_dir()">...</button></div></div>
<div><div class="lbl">Java Path</div><div style="display:flex; gap:8px"><input id="java" type="text" readonly><button style="padding:0 15px; border-radius:8px; border:1px solid var(--border); background:var(--panel); color:var(--text); cursor:pointer;" onclick="browse_java()">...</button></div></div>
<div class="theme-row"><div class="palette"><div class="dot active" style="background:#ffffff" onclick="clr('#ffffff',this)"></div><div class="dot" style="background:#00ff9d" onclick="clr('#00ff9d',this)"></div><div class="dot" style="background:#ff4757" onclick="clr('#ff4757',this)"></div><div class="dot" style="background:#1e90ff" onclick="clr('#1e90ff',this)"></div><div class="dot" style="background:#bf55ec" onclick="clr('#bf55ec',this)"></div><div class="color-picker-btn"><input type="color" oninput="clr(this.value, null)"></div></div><div class="mode-sw" onclick="toggleTheme()">THEME</div></div>
<div style="margin-top:auto"><button id="launch" onclick="go()">LAUNCH CLIENT</button><div class="bar-con"><div class="bar" id="bar"></div></div><div style="display:flex; justify-content:space-between; font-size:10px; color:#666; margin-top:5px; font-family:'JetBrains Mono';"><span id="st-txt">IDLE</span><span id="st-pct">0%</span></div></div>
<div class="term" id="term"></div>
</div>
<script>
let currentVer = "";
function toggleSelect(e) { e.stopPropagation(); document.getElementById('options-list').classList.toggle('show'); }
function closeAll() { document.getElementById('options-list').classList.remove('show'); }
function selectOpt(id, name, tag, mc) {
currentVer = id; let tagStr = tag ? ` <b style="color:var(--accent)">${tag}</b>` : "";
document.getElementById('selected-text').innerHTML = name + tagStr + ` <span class="v-tag">${mc}</span>`;
closeAll(); save();
}
function updateRam(v) { document.getElementById('ram-val').innerText = v + ' GB'; }
function toggleTheme() { document.body.classList.toggle('light'); save(); }
function clr(c, el) {
document.documentElement.style.setProperty('--accent', c);
document.querySelectorAll('.dot').forEach(d => d.classList.remove('active'));
if(el) el.classList.add('active'); save();
}
function browse_java() { pywebview.api.browse_java().then(p => { if(p) { document.getElementById('java').value = p; save(); }}); }
function browse_dir() { pywebview.api.browse_dir().then(p => { if(p) { document.getElementById('dl_path').value = p; save(); }}); }
function save() {
pywebview.api.save_cfg({nick: document.getElementById('nick').value, ver: currentVer, ram: document.getElementById('ram').value, java: document.getElementById('java').value, dl_path: document.getElementById('dl_path').value, theme: document.body.classList.contains('light')?'light':'dark', color: document.documentElement.style.getPropertyValue('--accent')});
}
function restore(d, versions, maxRam) {
const list = document.getElementById('options-list');
versions.forEach(v => {
const div = document.createElement('div'); div.className = 'option';
div.onclick = () => selectOpt(v.id, v.name, v.tag, v.mc);
div.innerHTML = `<span>${v.name}</span>${v.tag ? `<b style="color:var(--accent); font-weight:900;">${v.tag}</b>` : ""}<span class="v-tag">${v.mc}</span>`;
list.appendChild(div); if(d.ver === v.id) selectOpt(v.id, v.name, v.tag, v.mc);
});
document.getElementById('ram').max = maxRam;
if(d.nick) document.getElementById('nick').value = d.nick;
if(d.ram) { document.getElementById('ram').value = d.ram; updateRam(d.ram); }
if(d.java) document.getElementById('java').value = d.java;
if(d.dl_path) document.getElementById('dl_path').value = d.dl_path;
if(d.theme === 'light') document.body.classList.add('light');
if(d.color) clr(d.color, Array.from(document.querySelectorAll('.dot')).find(el => el.style.backgroundColor.includes(d.color)));
}
function go() { document.getElementById('launch').disabled = true; document.getElementById('term').classList.add('show'); document.getElementById('term').innerHTML = ''; pywebview.api.start(); }
function log(m, t) { const c = document.getElementById('term'); const d = document.createElement('div'); if(t==='err') d.style.color='#f55'; else if(t==='ok') d.style.color='#5f5'; d.innerText = '> ' + m; c.appendChild(d); c.scrollTop = c.scrollHeight; }
function prog(v, t) { document.getElementById('bar').style.width = v + '%'; document.getElementById('st-pct').innerText = Math.round(v) + '%'; if(t) document.getElementById('st-txt').innerText = t.toUpperCase(); }
function done() { document.getElementById('launch').disabled = false; }
</script>
</body>
</html>
"""

# ================= ЛОГИКА =================
class Api:
def init(self):
max_ram = int(psutil.virtual_memory().total / (1024**3))
self.cfg = self.load_settings()
if not self.cfg.get('java'): self.cfg['java'] = self.find_java()
if not self.cfg.get('dl_path'): self.cfg['dl_path'] = os.path.join(os.path.expanduser("~"), "Desktop")
v_list = [{"id": k, "name": v['name'], "tag": v.get('tag', ''), "mc": v['mc']} for k, v in CHECKS.items()]
window.evaluate_js(f'restore({json.dumps(self.cfg)}, {json.dumps(v_list)}, {max_ram})')

def load_settings(self):
if os.path.exists(SETTINGS_FILE):
try: return json.load(open(SETTINGS_FILE, 'r'))
except: return {}
return {}

def find_java(self):
for v in ["21", "17", "8"]:
p = f"C:\\Program Files\\Java\\jdk-{v}\\bin\\javaw.exe"
if os.path.exists(p): return p
return shutil.which("javaw") or ""

def save_cfg(self, d): self.cfg = d; open(SETTINGS_FILE, 'w').write(json.dumps(d))
def min(self): window.minimize()
def close(self): window.destroy()
def open_url(self, u): import webbrowser; webbrowser.open(u)
def browse_java(self):
res = window.create_file_dialog(webview.OPEN_DIALOG, file_types=('Executables (*.exe)',))
return res[0] if res else None
def browse_dir(self):
res = window.create_file_dialog(webview.FOLDER_DIALOG)
return res[0] if res else None

def log(self, m, t=''): window.evaluate_js(f'log({json.dumps(str(m))}, "{t}")')
def prog(self, v, t=None): window.evaluate_js(f'prog({v}, {json.dumps(t)})')

def _dl_v1(self, url, path, p_val, p_txt):
self.log(f"Downloading {p_txt}...")
self.prog(p_val, p_txt)
try:
r = requests.get(url, timeout=300)
if r.status_code == 200:
with open(path, 'wb') as f: f.write(r.content)
return True
return False
except Exception as e:
self.log(f"Error: {e}", "err"); return False

def _extract_v1(self, zip_path, target_dir, internal_folder=None):
"""Простая распаковка с опциональным переносом из внутренней папки."""
self.log(f"Extracting {os.path.basename(zip_path)}...")
temp_dir = os.path.join(target_dir, "_tmp_ext")
if os.path.exists(temp_dir): shutil.rmtree(temp_dir)
os.makedirs(temp_dir)

with zipfile.ZipFile(zip_path, 'r') as z:
z.extractall(temp_dir)

source = temp_dir
if internal_folder:
potential = os.path.join(temp_dir, internal_folder)
if os.path.exists(potential): source = potential

for item in os.listdir(source):
s, d = os.path.join(source, item), os.path.join(target_dir, item)
if os.path.isdir(s):
if os.path.exists(d): shutil.rmtree(d)
shutil.move(s, d)
else: shutil.move(s, d)
shutil.rmtree(temp_dir)

def start(self): threading.Thread(target=self._run, daemon=True).start()

def _run(self):
try:
info = CHECKS[self.cfg['ver']]
java, ram, nick = self.cfg['java'], self.cfg.get('ram', 4), self.cfg.get('nick', 'Player')
myst_dir = os.path.join(self.cfg['dl_path'], "MystLauncher")
os.makedirs(myst_dir, exist_ok=True)

if info['mode'] == 'fabric':
self._run_fabric(info, java, ram, nick, myst_dir)
else:
self._run_standard(info, java, ram, nick, myst_dir)
except Exception as e:
self.log(f"Launcher Error: {e}", "err")
window.evaluate_js('done()')

def _run_fabric(self, info, java, ram, nick, myst_dir):
# Папка чита: MystLauncher/Stellar/
cheat_dir = os.path.join(myst_dir, info['folder'])
os.makedirs(cheat_dir, exist_ok=True)
mc_dir = os.path.join(cheat_dir, ".minecraft")
os.makedirs(mc_dir, exist_ok=True)

# 1. Твоя база .minecraft.zip
if not os.path.exists(os.path.join(mc_dir, "libraries")):
zip_p = os.path.join(cheat_dir, "base.zip")
if self._dl_v1(URL_BASE_MC, zip_p, 10, "Base Minecraft"):
self._extract_v1(zip_p, cheat_dir, ".minecraft")
os.remove(zip_p)

# 2. Твой Fabric Zip
if not os.path.exists(os.path.join(mc_dir, "versions")):
f_url = URL_FABRIC_1218 if "1.21.8" in info['mc'] else URL_FABRIC_1214
f_zip = os.path.join(cheat_dir, "fabric.zip")
f_folder = f"Fabric {info['mc']}"
if self._dl_v1(f_url, f_zip, 40, "Fabric Components"):
self._extract_v1(f_zip, mc_dir, f_folder)
os.remove(f_zip)

# 3. Мод и API
mods_dir = os.path.join(mc_dir, "mods")
os.makedirs(mods_dir, exist_ok=True)
self._dl_v1(info['api'], os.path.join(mods_dir, "fabric-api.jar"), 70, "API")
self._dl_v1(info['url'], os.path.join(mods_dir, "client.jar"), 85, "Cheat Mod")

v_folder = os.path.join(mc_dir, "versions")
v_id = next(d for d in os.listdir(v_folder) if "fabric" in d.lower())
self._launch(v_id, java, ram, nick, mc_dir)

def _run_standard(self, info, java, ram, nick, myst_dir):
# 1.16.5 в общую папку MystLauncher/
mc_dir = os.path.join(myst_dir, ".minecraft")
os.makedirs(mc_dir, exist_ok=True)

if not os.path.exists(os.path.join(mc_dir, "libraries")):
zip_p = os.path.join(myst_dir, "base_legacy.zip")
if self._dl_v1(URL_BASE_MC, zip_p, 10, "Base Assets"):
self._extract_v1(zip_p, myst_dir, ".minecraft")
os.remove(zip_p)

target = os.path.join(mc_dir, "versions", info['folder'])
jar = os.path.join(target, f"{info['folder']}.jar")
if not os.path.exists(jar):
zip_p = os.path.join(myst_dir, "cheat_116.zip")
if self._dl_v1(info['url'], zip_p, 60, "Cheat Files"):
os.makedirs(target, exist_ok=True)
with zipfile.ZipFile(zip_p, 'r') as z: z.extractall(target)
os.remove(zip_p)
for r, d, files in os.walk(target):
for f in files:
if f.endswith(".jar"): shutil.move(os.path.join(r, f), jar); break

self._launch(info['folder'], java, ram, nick, mc_dir)

def _launch(self, v_id, java, ram, nick, g_dir):
self.prog(95, "Ready")
# Поиск библиотек в правильной папке
opts = {
"username": nick, "uuid": "0", "token": "0", "executablePath": java,
"gameDirectory": g_dir,
"jvmArguments": [f"-Xmx{ram}G", "-noverify", "-Dfile.encoding=UTF-8"]
}
try:
# Важно: minecraft_launcher_lib должна знать, где искать библиотеки
cmd = minecraft_launcher_lib.command.get_minecraft_command(v_id, g_dir, opts)

# Фикс для jopt-simple (для 1.16.5)
lib_j = os.path.join(g_dir, "libraries", "net", "sf", "jopt-simple", "jopt-simple", "5.0.4", "jopt-simple-5.0.4.jar")
if os.path.exists(lib_j):
cp_idx = cmd.index("-cp") + 1
if lib_j not in cmd[cp_idx]: cmd[cp_idx] += os.pathsep + lib_j

self.log(f"Launching {v_id}...", "ok"); self.prog(100, "Launched")
subprocess.Popen(cmd, cwd=g_dir)
except Exception as e:
self.log(f"Launch Error: {e}", "err")

if __name__ == '__main__':
api = Api()
window = webview.create_window("Myst Launcher v7", html=HTML, width=420, height=720, frameless=True, js_api=api)
webview.start(api.init)
Пиздец
 
Python:
Expand Collapse Copy
import os
import sys
import threading
import time
import zipfile
import shutil
import subprocess
import json
import requests
import locale
import ctypes
import psutil

# ================= ССЫЛКИ НА АРХИВЫ (ОБЪЯВЛЕНЫ ПЕРВЫМИ) =================
URL_BASE_MC = "https://www.dropbox.com/scl/fi/21wg...ey=rkwzqt1hvgyfw36a07kltmyj4&st=7226dm0g&dl=1"
URL_FABRIC_1214 = "https://www.dropbox.com/scl/fi/14ne...ey=oy9xsokpyy9cksy7g27cgfw6k&st=hhosebmn&dl=1"
URL_FABRIC_1218 = "https://www.dropbox.com/scl/fi/scxn...ey=nz0wqil403ni34zgcob7g215g&st=039t5nvn&dl=1"

F_API_1_21_4 = "https://www.dropbox.com/scl/fi/j2ik...ey=ot5hxc7gs5twagtp7qcyvforw&st=zn8hr29e&dl=1"
F_API_1_21_8 = "https://www.dropbox.com/scl/fi/rb5g...ey=cq2eopcp4j0vzn947v7cele9z&st=37ummjn0&dl=1"

# ================= СИСТЕМА =================
if sys.platform == "win32":
    ctypes.windll.kernel32.SetConsoleOutputCP(65001)
try:
    locale.setlocale(locale.LC_ALL, 'ru_RU.UTF-8')
except:
    locale.setlocale(locale.LC_ALL, '')


def install_libs():
    for lib in ["minecraft_launcher_lib", "pywebview"]:
        try:
            __import__(lib.replace("-", "_"))
        except ImportError:
            subprocess.check_call([sys.executable, "-m", "pip", "install", lib, "--quiet"])


install_libs()
import webview
import minecraft_launcher_lib

# ================= КЛИЕНТЫ =================
CHECKS = {
    "soul": {"name": "SoulDLC", "tag": "OLD", "url": "https://www.dropbox.com/scl/fi/iq24...ey=ekqk89x5aidoxumeuezrxm5yz&st=1uejtump&dl=1", "folder": "soul", "mode": "clone", "mc": "1.16.5"},
    "venus": {"name": "VenusFree", "url": "https://www.dropbox.com/scl/fi/ihru...ey=6wyce00cg90z4mo6ijqm28r39&st=01ngi5hd&dl=1", "folder": "VenusFree", "mode": "clone", "mc": "1.16.5"},
    "nightdlc113": {"name": "NightDLC 1.1.3", "url": "https://www.dropbox.com/scl/fi/kxrr...ey=4lzlp21y7d3nrr5rx4addt3w0&st=1chab12z&dl=1", "folder": "nightdlc113", "mode": "clone", "mc": "1.16.5"},
    "nightdlc": {"name": "NightDLC 1.1.2", "url": "https://www.dropbox.com/scl/fi/jdbk...ey=zfal5osilppsxtsl291lc7u8p&st=uoc0ddk6&dl=1", "folder": "night", "mode": "clone", "mc": "1.16.5"},
    "dimasik": {"name": "Dimasik", "tag": "OLD", "url": "https://www.dropbox.com/scl/fi/mwyq...ey=x26473lzsrkrh20xgigfhxxl6&st=97dyxnrc&dl=1", "folder": "dimasik", "mode": "clone", "mc": "1.16.5"},
    "neverlose": {"name": "NeverLose", "tag": "OLD", "url": "https://www.dropbox.com/scl/fi/c0fz...ey=tpa381djsfdhpxp0fr2628mql&st=3l6dz7cz&dl=1", "folder": "NeverLose", "mode": "clone", "mc": "1.16.5"},
    "javelin": {"name": "Javelin", "tag": "1.21.8", "url": "https://www.dropbox.com/scl/fi/d98y...ey=gzjknzi77ncvpme76j2quiuvw&st=3ta9cnx0&dl=1", "folder": "Javelin", "mode": "fabric", "mc": "1.21.8", "api": F_API_1_21_8},
    "rockstar": {"name": "RockStar", "tag": "1.21.4", "url": "https://www.dropbox.com/scl/fi/deaw...ey=lwd50zwkby7vgedktp5t3pwdm&st=3o0ydktc&dl=1", "folder": "RockStar", "mode": "fabric", "mc": "1.21.4", "api": F_API_1_21_4},
    "stellar": {"name": "Stellar", "tag": "1.21.4", "url": "https://www.dropbox.com/scl/fi/xs0s...ey=obh0oml6brahdmzeuwafhcs54&st=9g3hwwpz&dl=1", "folder": "Stellar", "mode": "fabric", "mc": "1.21.4", "api": F_API_1_21_4},
    "wild": {"name": "Wild", "tag": "1.21.4", "url": "https://www.dropbox.com/scl/fi/reov...ey=vuws2o72nsij5p85mlxyt5qya&st=rvi5r9ga&dl=1", "folder": "Wild", "mode": "fabric", "mc": "1.21.4", "api": F_API_1_21_4},
}

SETTINGS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "settings.json")

# ================= HTML =================
HTML = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
@import url('https://fonts.googleapis.com/css2?f...y=JetBrains+Mono:wght@500;700&display=swap');
:root { --bg: #0b0b0b; --panel: #141414; --border: #2a2a2a; --accent: #ffffff; --text: #e0e0e0; }
body.light { --bg: #f5f5f5; --panel: #ffffff; --border: #ddd; --text: #111; }
body { background: var(--bg); color: var(--text); font-family: 'Inter'; margin: 0; padding: 0; height: 100vh; display: flex; flex-direction: column; overflow: hidden; user-select: none; }
.head { height: 45px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; padding: 0 16px; -webkit-app-region: drag; background: var(--panel); }
.head-btns { display: flex; align-items: center; gap: 8px; -webkit-app-region: no-drag; }
.win-btn { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #666; transition: 0.2s; border-radius: 6px; }
.win-btn:hover { color: var(--accent); background: var(--border); }
.win-btn svg { width: 22px; height: 22px; fill: currentColor; }
.body { flex: 1; padding: 20px; display: flex; flex-direction: column; gap: 12px; -webkit-app-region: no-drag; box-sizing: border-box; }
.lbl { font-size: 11px; font-weight: 800; color: #666; text-transform: uppercase; margin-bottom: 2px; }
.custom-select { position: relative; width: 100%; box-sizing: border-box; }
.select-trigger { padding: 12px; border-radius: 8px; background: var(--bg); border: 1px solid var(--border); color: var(--text); font-family: 'JetBrains Mono'; font-size: 12px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; box-sizing: border-box; }
.select-options { position: absolute; top: 100%; left: 0; right: 0; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; margin-top: 5px; max-height: 180px; overflow-y: auto; display: none; z-index: 100; box-shadow: 0 5px 15px rgba(0,0,0,0.5); }
.select-options.show { display: block; }
.option { padding: 10px 12px; font-family: 'JetBrains Mono'; font-size: 12px; cursor: pointer; display: flex; align-items: center; }
.option:hover { background: var(--border); }
.option b, .select-trigger b { color: var(--accent) !important; font-weight: 900 !important; margin: 0 6px; text-transform: uppercase; }
.v-tag { color: #666; font-size: 10px; margin-left: auto; }
input { width: 100%; padding: 12px; border-radius: 8px; background: var(--bg); border: 1px solid var(--border); color: var(--text); font-family: 'JetBrains Mono'; font-size: 12px; outline: none; box-sizing: border-box; }
.ram-box { padding: 5px 0; -webkit-app-region: no-drag !important; }
input[type=range] { -webkit-appearance: none; width: 100%; height: 2px; background: var(--border); outline: none; -webkit-app-region: no-drag !important; }
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; background: var(--accent); border-radius: 50%; cursor: pointer; border: 4px solid var(--panel); -webkit-app-region: no-drag !important; }
.theme-row { display: flex; justify-content: space-between; align-items: center; -webkit-app-region: no-drag; }
.palette { display: flex; gap: 8px; align-items: center; }
.dot { width: 18px; height: 18px; border-radius: 50%; cursor: pointer; border: 2px solid transparent; }
.dot.active { border-color: var(--text); transform: scale(1.1); }
.color-picker-btn { width: 18px; height: 18px; border-radius: 50%; background: linear-gradient(45deg,red,orange,yellow,green,cyan,blue,violet); cursor: pointer; border: 1px solid var(--border); overflow: hidden; position: relative; }
.color-picker-btn input { position: absolute; opacity: 0; width: 100%; height: 100%; cursor: pointer; top:0; left:0; }
.mode-sw { font-size: 10px; font-weight: 800; cursor: pointer; padding: 6px 12px; background: var(--border); border-radius: 6px; }
#launch { width: 100%; padding: 16px; border: none; border-radius: 10px; background: var(--accent); color: #000; font-weight: 800; font-size: 14px; cursor: pointer; margin-top: auto; -webkit-app-region: no-drag; }
.bar-con { height: 4px; background: var(--border); margin-top: 15px; border-radius: 2px; overflow: hidden; }
.bar { height: 100%; width: 0%; background: var(--accent); transition: 0.3s; }
.term { width: 100%; height: 100px; background: #080808; border: 1px solid var(--border); border-radius: 8px; padding: 10px; font-family: 'JetBrains Mono'; font-size: 10px; color: #bbb; overflow-y: auto; display: none; margin-top: 10px; box-sizing: border-box; word-break: break-all; white-space: pre-wrap; }
.term.show { display: block; }
</style>
</head>
<body onclick="closeAll()">
<div class="head">
<div style="font-weight:800; font-size:13px;">MYST LAUNCHER v7</div>
<div class="head-btns">
<div class="win-btn" onclick="pywebview.api.open_url('https://t.me/MystLauncher')"><svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69a.2.2 0 00-.05-.18c-.06-.05-.14-.03-.21-.02-.09.02-1.49.95-4.22 2.79-.4.27-.76.41-1.08.4-.36-.01-1.04-.2-1.55-.37-.63-.2-1.12-.31-1.08-.66.02-.18.27-.36.74-.55 2.92-1.27 4.86-2.11 5.83-2.51 2.78-1.16 3.35-1.36 3.73-1.36.08 0 .27.02.39.12.1.08.13.19.14.27-.01.06.01.24 0 .38z"/></svg></div>
<div class="win-btn" onclick="pywebview.api.open_url('https://dsc.gg/MystLauncher')"><svg viewBox="0 0 24 24"><path fill="currentColor" d="M19.73 4.87a18.2 18.2 0 0 0-4.6-1.44c-.21.4-.4.8-.58 1.21a17.1 17.1 0 0 0-5.1 0 13.5 13.5 0 0 0-.58-1.21 18.2 18.2 0 0 0-4.6 1.44A18.8 18.8 0 0 0 .12 18.05c2.42 1.78 4.76 2.86 7.07 3.58.54-.74 1.01-1.53 1.41-2.37a11.6 11.6 0 0 1-2.18-1.1c.14-.11.29-.22.43-.34 4.66 2.13 9.7 2.13 14.3 0 .14.11.29.23.43.34-.68.44-1.41.81-2.18 1.1.4.84 1.01 1.54 1.41 2.37 2.3-.71 4.65-1.8 7.07-3.58a18.8 18.8 0 0 0-3.66-13.18zM8.02 15.33c-1.18 0-2.16-1.08-2.16-2.42s.96-2.42 2.16-2.42c1.21 0 2.18 1.1 2.16 2.42 0 1.33-.96 2.42-2.16 2.42zm7.97 0c-1.18 0-2.16-1.08-2.16-2.42s.96-2.42 2.16-2.42c1.21 0 2.18 1.1 2.16 2.42 0 1.33-.95 2.42-2.16 2.42z"/></svg></div>
<div style="width:10px"></div>
<div class="win-btn" onclick="pywebview.api.min()">_</div>
<div class="win-btn" onclick="pywebview.api.close()">✕</div>
</div>
</div>
<div class="body">
<div><div class="lbl">Nickname</div><input id="nick" type="text" placeholder="Username" onchange="save()"></div>
<div><div class="lbl">Client Version</div><div class="custom-select" id="custom-ver"><div class="select-trigger" onclick="toggleSelect(event)"><span id="selected-text">Select Client</span><span>▼</span></div><div class="select-options" id="options-list"></div></div></div>
<div class="ram-box"><div class="lbl">RAM ALLOCATION <span id="ram-val" style="color:var(--accent); font-weight:900;">4 GB</span></div><input type="range" id="ram" min="1" max="16" step="1" oninput="updateRam(this.value)" onchange="save()"></div>
<div><div class="lbl">Install Path</div><div style="display:flex; gap:8px"><input id="dl_path" type="text" readonly><button style="padding:0 15px; border-radius:8px; border:1px solid var(--border); background:var(--panel); color:var(--text); cursor:pointer;" onclick="browse_dir()">...</button></div></div>
<div><div class="lbl">Java Path</div><div style="display:flex; gap:8px"><input id="java" type="text" readonly><button style="padding:0 15px; border-radius:8px; border:1px solid var(--border); background:var(--panel); color:var(--text); cursor:pointer;" onclick="browse_java()">...</button></div></div>
<div class="theme-row"><div class="palette"><div class="dot active" style="background:#ffffff" onclick="clr('#ffffff',this)"></div><div class="dot" style="background:#00ff9d" onclick="clr('#00ff9d',this)"></div><div class="dot" style="background:#ff4757" onclick="clr('#ff4757',this)"></div><div class="dot" style="background:#1e90ff" onclick="clr('#1e90ff',this)"></div><div class="dot" style="background:#bf55ec" onclick="clr('#bf55ec',this)"></div><div class="color-picker-btn"><input type="color" oninput="clr(this.value, null)"></div></div><div class="mode-sw" onclick="toggleTheme()">THEME</div></div>
<div style="margin-top:auto"><button id="launch" onclick="go()">LAUNCH CLIENT</button><div class="bar-con"><div class="bar" id="bar"></div></div><div style="display:flex; justify-content:space-between; font-size:10px; color:#666; margin-top:5px; font-family:'JetBrains Mono';"><span id="st-txt">IDLE</span><span id="st-pct">0%</span></div></div>
<div class="term" id="term"></div>
</div>
<script>
let currentVer = "";
function toggleSelect(e) { e.stopPropagation(); document.getElementById('options-list').classList.toggle('show'); }
function closeAll() { document.getElementById('options-list').classList.remove('show'); }
function selectOpt(id, name, tag, mc) {
currentVer = id; let tagStr = tag ? ` <b style="color:var(--accent)">${tag}</b>` : "";
document.getElementById('selected-text').innerHTML = name + tagStr + ` <span class="v-tag">${mc}</span>`;
closeAll(); save();
}
function updateRam(v) { document.getElementById('ram-val').innerText = v + ' GB'; }
function toggleTheme() { document.body.classList.toggle('light'); save(); }
function clr(c, el) {
document.documentElement.style.setProperty('--accent', c);
document.querySelectorAll('.dot').forEach(d => d.classList.remove('active'));
if(el) el.classList.add('active'); save();
}
function browse_java() { pywebview.api.browse_java().then(p => { if(p) { document.getElementById('java').value = p; save(); }}); }
function browse_dir() { pywebview.api.browse_dir().then(p => { if(p) { document.getElementById('dl_path').value = p; save(); }}); }
function save() {
pywebview.api.save_cfg({nick: document.getElementById('nick').value, ver: currentVer, ram: document.getElementById('ram').value, java: document.getElementById('java').value, dl_path: document.getElementById('dl_path').value, theme: document.body.classList.contains('light')?'light':'dark', color: document.documentElement.style.getPropertyValue('--accent')});
}
function restore(d, versions, maxRam) {
const list = document.getElementById('options-list');
versions.forEach(v => {
const div = document.createElement('div'); div.className = 'option';
div.onclick = () => selectOpt(v.id, v.name, v.tag, v.mc);
div.innerHTML = `<span>${v.name}</span>${v.tag ? `<b style="color:var(--accent); font-weight:900;">${v.tag}</b>` : ""}<span class="v-tag">${v.mc}</span>`;
list.appendChild(div); if(d.ver === v.id) selectOpt(v.id, v.name, v.tag, v.mc);
});
document.getElementById('ram').max = maxRam;
if(d.nick) document.getElementById('nick').value = d.nick;
if(d.ram) { document.getElementById('ram').value = d.ram; updateRam(d.ram); }
if(d.java) document.getElementById('java').value = d.java;
if(d.dl_path) document.getElementById('dl_path').value = d.dl_path;
if(d.theme === 'light') document.body.classList.add('light');
if(d.color) clr(d.color, Array.from(document.querySelectorAll('.dot')).find(el => el.style.backgroundColor.includes(d.color)));
}
function go() { document.getElementById('launch').disabled = true; document.getElementById('term').classList.add('show'); document.getElementById('term').innerHTML = ''; pywebview.api.start(); }
function log(m, t) { const c = document.getElementById('term'); const d = document.createElement('div'); if(t==='err') d.style.color='#f55'; else if(t==='ok') d.style.color='#5f5'; d.innerText = '> ' + m; c.appendChild(d); c.scrollTop = c.scrollHeight; }
function prog(v, t) { document.getElementById('bar').style.width = v + '%'; document.getElementById('st-pct').innerText = Math.round(v) + '%'; if(t) document.getElementById('st-txt').innerText = t.toUpperCase(); }
function done() { document.getElementById('launch').disabled = false; }
</script>
</body>
</html>
"""


# ================= ЛОГИКА =================
class Api:
    def __init__(self):
        self.cfg = {}
        self._window_ready = False

    def init(self):
        max_ram = int(psutil.virtual_memory().total / (1024 ** 3))
        self.cfg = self.load_settings()
        if not self.cfg.get('java'):
            self.cfg['java'] = self.find_java()
        if not self.cfg.get('dl_path'):
            self.cfg['dl_path'] = os.path.join(os.path.expanduser("~"), "Desktop")
        v_list = [{"id": k, "name": v['name'], "tag": v.get('tag', ''), "mc": v['mc']} for k, v in CHECKS.items()]
        window.evaluate_js(f'restore({json.dumps(self.cfg)}, {json.dumps(v_list)}, {max_ram})')
        self._window_ready = True

    def load_settings(self):
        if os.path.exists(SETTINGS_FILE):
            try:
                with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
                    return json.load(f)
            except:
                return {}
        return {}

    def find_java(self):
        for v in ["21", "17", "8"]:
            p = f"C:\\Program Files\\Java\\jdk-{v}\\bin\\javaw.exe"
            if os.path.exists(p):
                return p
        return shutil.which("javaw") or ""

    def save_cfg(self, d):
        self.cfg = d
        with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
            json.dump(d, f, ensure_ascii=False)

    def min(self):
        window.minimize()

    def close(self):
        window.destroy()

    def open_url(self, u):
        import webbrowser
        webbrowser.open(u)

    def browse_java(self):
        res = window.create_file_dialog(webview.OPEN_DIALOG, file_types=('Executables (*.exe)',))
        return res[0] if res else None

    def browse_dir(self):
        res = window.create_file_dialog(webview.FOLDER_DIALOG)
        return res[0] if res else None

    def log(self, m, t=''):
        window.evaluate_js(f'log({json.dumps(str(m))}, "{t}")')

    def prog(self, v, t=None):
        window.evaluate_js(f'prog({v}, {json.dumps(t)})')

    def _dl_v1(self, url, path, p_val, p_txt):
        self.log(f"Downloading {p_txt}...")
        self.prog(p_val, p_txt)
        try:
            r = requests.get(url, timeout=300)
            if r.status_code == 200:
                with open(path, 'wb') as f:
                    f.write(r.content)
                return True
            self.log(f"HTTP {r.status_code}", "err")
            return False
        except Exception as e:
            self.log(f"Error: {e}", "err")
            return False

    def _merge_to(self, src, dst):
        for item in os.listdir(src):
            s = os.path.join(src, item)
            d = os.path.join(dst, item)
            if os.path.isdir(s):
                if os.path.exists(d):
                    self._merge_to(s, d)
                else:
                    shutil.move(s, d)
            else:
                shutil.move(s, d)

    def _extract_v1(self, zip_path, target_dir, internal_folder=None):
        self.log(f"Extracting {os.path.basename(zip_path)}...")
        temp_dir = os.path.join(target_dir, "_tmp_ext")
        if os.path.exists(temp_dir):
            shutil.rmtree(temp_dir)
        os.makedirs(temp_dir, exist_ok=True)

        with zipfile.ZipFile(zip_path, 'r') as z:
            z.extractall(temp_dir)

        source = temp_dir
        if internal_folder:
            potential = os.path.join(temp_dir, internal_folder)
            if os.path.exists(potential):
                source = potential

        os.makedirs(target_dir, exist_ok=True)
        self._merge_to(source, target_dir)
        shutil.rmtree(temp_dir)

    def start(self):
        threading.Thread(target=self._run, daemon=True).start()

    def _run(self):
        try:
            info = CHECKS[self.cfg['ver']]
            java = self.cfg['java']
            ram = self.cfg.get('ram', 4)
            nick = self.cfg.get('nick', 'Player')
            myst_dir = os.path.join(self.cfg['dl_path'], "MystLauncher")
            os.makedirs(myst_dir, exist_ok=True)

            if info['mode'] == 'fabric':
                self._run_fabric(info, java, ram, nick, myst_dir)
            else:
                self._run_standard(info, java, ram, nick, myst_dir)
        except Exception as e:
            self.log(f"Launcher Error: {e}", "err")
            window.evaluate_js('done()')

    def _run_fabric(self, info, java, ram, nick, myst_dir):
        cheat_dir = os.path.join(myst_dir, info['folder'])
        os.makedirs(cheat_dir, exist_ok=True)
        mc_dir = os.path.join(cheat_dir, ".minecraft")
        os.makedirs(mc_dir, exist_ok=True)

        # 1. База .minecraft.zip -> mc_dir
        if not os.path.exists(os.path.join(mc_dir, "libraries")):
            zip_p = os.path.join(cheat_dir, "base.zip")
            if self._dl_v1(URL_BASE_MC, zip_p, 10, "Base Minecraft"):
                self._extract_v1(zip_p, mc_dir, ".minecraft")
                os.remove(zip_p)

        # 2. Fabric -> mc_dir
        if not os.path.exists(os.path.join(mc_dir, "versions")):
            f_url = URL_FABRIC_1218 if "1.21.8" in info['mc'] else URL_FABRIC_1214
            f_zip = os.path.join(cheat_dir, "fabric.zip")
            f_folder = f"Fabric {info['mc']}"
            if self._dl_v1(f_url, f_zip, 40, "Fabric Components"):
                self._extract_v1(f_zip, mc_dir, f_folder)
                os.remove(f_zip)

        # 3. Мод и API
        mods_dir = os.path.join(mc_dir, "mods")
        os.makedirs(mods_dir, exist_ok=True)
        self._dl_v1(info['api'], os.path.join(mods_dir, "fabric-api.jar"), 70, "API")
        self._dl_v1(info['url'], os.path.join(mods_dir, "client.jar"), 85, "Cheat Mod")

        v_folder = os.path.join(mc_dir, "versions")
        v_id = next(d for d in os.listdir(v_folder) if "fabric" in d.lower())
        self._launch(v_id, java, ram, nick, mc_dir)

    def _run_standard(self, info, java, ram, nick, myst_dir):
        mc_dir = os.path.join(myst_dir, ".minecraft")
        os.makedirs(mc_dir, exist_ok=True)

        # 1. База -> mc_dir
        if not os.path.exists(os.path.join(mc_dir, "libraries")):
            zip_p = os.path.join(myst_dir, "base_legacy.zip")
            if self._dl_v1(URL_BASE_MC, zip_p, 10, "Base Assets"):
                self._extract_v1(zip_p, mc_dir, ".minecraft")
                os.remove(zip_p)

        # 2. Чит
        target = os.path.join(mc_dir, "versions", info['folder'])
        jar = os.path.join(target, f"{info['folder']}.jar")
        if not os.path.exists(jar):
            os.makedirs(target, exist_ok=True)
            zip_p = os.path.join(myst_dir, "cheat_116.zip")
            if self._dl_v1(info['url'], zip_p, 60, "Cheat Files"):
                with zipfile.ZipFile(zip_p, 'r') as z:
                    z.extractall(target)
                os.remove(zip_p)
                jar_found = False
                for r, d, files in os.walk(target):
                    for f in files:
                        if f.endswith(".jar"):
                            shutil.move(os.path.join(r, f), jar)
                            jar_found = True
                            break
                    if jar_found:
                        break

        self._launch(info['folder'], java, ram, nick, mc_dir)

    def _launch(self, v_id, java, ram, nick, g_dir):
        self.prog(95, "Ready")
        opts = {
            "username": nick,
            "uuid": "0",
            "token": "0",
            "executablePath": java,
            "gameDirectory": g_dir,
            "jvmArguments": [f"-Xmx{ram}G", "-noverify", "-Dfile.encoding=UTF-8"]
        }
        try:
            cmd = minecraft_launcher_lib.command.get_minecraft_command(v_id, g_dir, opts)

            lib_j = os.path.join(g_dir, "libraries", "net", "sf", "jopt-simple", "jopt-simple", "5.0.4", "jopt-simple-5.0.4.jar")
            if os.path.exists(lib_j):
                cp_idx = cmd.index("-cp") + 1
                if lib_j not in cmd[cp_idx]:
                    cmd[cp_idx] += os.pathsep + lib_j

            self.log(f"Launching {v_id}...", "ok")
            self.prog(100, "Launched")
            subprocess.Popen(cmd, cwd=g_dir)
        except Exception as e:
            self.log(f"Launch Error: {e}", "err")
        finally:
            window.evaluate_js('done()')


if __name__ == '__main__':
    api = Api()
    window = webview.create_window("Myst Launcher v7", html=HTML, width=420, height=720, frameless=True, js_api=api)
    webview.start(api.init)
дальше как я понимаю проблема уже в ссылках
1778651067136.png
 
Назад
Сверху Снизу