From e5948b53376c14f5e66bafd5c8852527a3dc7453 Mon Sep 17 00:00:00 2001 From: SashegDev Date: Fri, 8 May 2026 10:11:49 +0000 Subject: [PATCH] Fix: Multiple launcher issues - Fix CLI arrow keys: remove 50ms timeout in escape sequence handling (ArrowMenu, LoginMenu) - Add network logs polling to UI via /api/logs endpoint - Display user role in launcher header (AuthManager, AuthService, JFXLauncher, UI) - Capture and display game logs in launcher via /api/game-logs endpoint - Fix demo mode bug in VersionManifest.ruleMatches() - was incorrectly adding --demo flag - Fix modloader launch: pass proper auth info (accessToken, uuid) from AuthManager - Add game log capture in MinecraftLib and LaunchService --- launcher/bootstrap/pom.xml | 2 +- .../sashegdev/zernmc/launcher/Bootstrap.java | 18 +++++++ launcher/launcher/pom.xml | 21 ++++++-- .../zernmc/launcher/api/auth/AuthService.java | 12 ++++- .../launcher/api/launch/LaunchService.java | 38 +++++++++++++- .../zernmc/launcher/auth/AuthManager.java | 7 +++ .../zernmc/launcher/menu/LoginMenu.java | 4 +- .../launcher/minecraft/MinecraftLib.java | 38 ++++++++++++-- .../minecraft/launch/VersionManifest.java | 11 +++- .../zernmc/launcher/ui/ArrowMenu.java | 4 +- .../zernmc/launcher/ui/jfx/JFXLauncher.java | 48 +++++++++++++++--- launcher/launcher/src/resources/ui/index.html | 1 + .../launcher/src/resources/ui/launcher.js | 38 ++++++++++++++ launcher/launcher/src/resources/ui/style.css | 5 ++ launcher/pom.xml | 2 +- server/main.py | 50 +++++++++++++++++-- 16 files changed, 271 insertions(+), 28 deletions(-) diff --git a/launcher/bootstrap/pom.xml b/launcher/bootstrap/pom.xml index 20cc5ce..60792f9 100644 --- a/launcher/bootstrap/pom.xml +++ b/launcher/bootstrap/pom.xml @@ -7,7 +7,7 @@ me.sashegdev ZernMCLauncher - 1.0.8 + 1.0.9 zernmc-bootstrap diff --git a/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java b/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java index 404ab90..6f451a7 100644 --- a/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java +++ b/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java @@ -8,6 +8,8 @@ import java.security.MessageDigest; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.jar.Attributes; +import java.util.jar.Manifest; public class Bootstrap { private static final String VERSION_FILE = "build.version"; @@ -62,10 +64,26 @@ public class Bootstrap { } private static String readCurrentVersion() { + // Читаем версию из манифеста zernmclauncher.jar в папке bin/ + Path launcherJar = baseDir.resolve("bin").resolve("zernmclauncher.jar"); + try { + if (Files.exists(launcherJar)) { + try (FileInputStream fis = new FileInputStream(launcherJar.toFile())) { + Manifest manifest = new Manifest(fis); + String version = manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION); + if (version != null && !version.isBlank()) { + return version; + } + } + } + } catch (Exception ignored) {} + + // Fallback: из build.version Path f = baseDir.resolve(VERSION_FILE); try { if (Files.exists(f)) return Files.readString(f).trim(); } catch (Exception ignored) {} + return "0.0.0"; } diff --git a/launcher/launcher/pom.xml b/launcher/launcher/pom.xml index d5ae1ab..5af03f8 100644 --- a/launcher/launcher/pom.xml +++ b/launcher/launcher/pom.xml @@ -7,7 +7,7 @@ me.sashegdev ZernMCLauncher - 1.0.8 + 1.0.9 zernmclauncher @@ -223,11 +223,24 @@ - + + + + + + + + + + + + + + includes="zernmc.exe,bin/**,assets/**,lib/**" + excludes="build.version,*-${project.version}.*,zernmclauncher.jar,zernmc-bootstrap.jar"/> diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/auth/AuthService.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/auth/AuthService.java index 48097c0..b74c945 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/auth/AuthService.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/auth/AuthService.java @@ -37,7 +37,9 @@ public class AuthService { SessionInfo info = new SessionInfo( AuthManager.getUsername(), AuthManager.getAccessToken(), - AuthManager.hasActivePass() + AuthManager.hasActivePass(), + AuthManager.getRole(), + AuthManager.getRoleName() ); return ApiResponse.success(info); } @@ -120,15 +122,21 @@ public class AuthService { private String username; private String token; private boolean passActive; + private int role; + private String roleName; - public SessionInfo(String username, String token, boolean passActive) { + public SessionInfo(String username, String token, boolean passActive, int role, String roleName) { this.username = username; this.token = token; this.passActive = passActive; + this.role = role; + this.roleName = roleName; } public String getUsername() { return username; } public String getToken() { return token; } public boolean isPassActive() { return passActive; } + public int getRole() { return role; } + public String getRoleName() { return roleName; } } } diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/launch/LaunchService.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/launch/LaunchService.java index c203138..157a413 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/launch/LaunchService.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/launch/LaunchService.java @@ -1,12 +1,16 @@ package me.sashegdev.zernmc.launcher.api.launch; import me.sashegdev.zernmc.launcher.api.ApiResponse; +import me.sashegdev.zernmc.launcher.auth.AuthManager; import me.sashegdev.zernmc.launcher.minecraft.Instance; import me.sashegdev.zernmc.launcher.minecraft.InstanceManager; import me.sashegdev.zernmc.launcher.minecraft.launch.LaunchCommandBuilder; import me.sashegdev.zernmc.launcher.minecraft.model.LaunchOptions; +import me.sashegdev.zernmc.launcher.ui.jfx.JFXLauncher; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.nio.file.Path; import java.util.List; @@ -44,15 +48,47 @@ public class LaunchService { LaunchCommandBuilder builder = new LaunchCommandBuilder(instance); LaunchOptions options = new LaunchOptions(); + + // Set auth info + options.setUsername(AuthManager.getUsername()); + options.setAccessToken(AuthManager.getAccessToken()); + options.setUuid(AuthManager.getUuid()); List command = builder.build(options); ProcessBuilder processBuilder = new ProcessBuilder(command); processBuilder.directory(instance.getPath().toFile()); - processBuilder.inheritIO(); Process process = processBuilder.start(); + // Capture output + Thread outThread = new Thread(() -> { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + JFXLauncher.appendGameLog(line); + } + } catch (Exception e) { + JFXLauncher.appendGameLog("[Ошибка чтения вывода: " + e.getMessage() + "]"); + } + }); + outThread.setDaemon(true); + outThread.start(); + + // Capture errors + Thread errThread = new Thread(() -> { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { + String line; + while ((line = reader.readLine()) != null) { + JFXLauncher.appendGameLog("[ERR] " + line); + } + } catch (Exception e) { + JFXLauncher.appendGameLog("[Ошибка чтения ошибок: " + e.getMessage() + "]"); + } + }); + errThread.setDaemon(true); + errThread.start(); + ProcessInfo info = new ProcessInfo( instanceName, process.pid(), diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/auth/AuthManager.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/auth/AuthManager.java index d169b23..7e30a01 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/auth/AuthManager.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/auth/AuthManager.java @@ -213,6 +213,13 @@ public class AuthManager { return session != null ? session.role : ROLE_USER; } + public static String getRoleName() { + if (userInfo != null && userInfo.role_name != null) { + return userInfo.role_name; + } + return "USER"; + } + // ====================== POST ====================== private static SimpleHttpResponse post(String endpoint, String jsonBody) throws Exception { String fullUrl = ZHttpClient.getBaseUrl() + endpoint; diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/menu/LoginMenu.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/menu/LoginMenu.java index 6a106a6..2115e2a 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/menu/LoginMenu.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/menu/LoginMenu.java @@ -166,9 +166,9 @@ public class LoginMenu { if (key == 27) { // Escape sequence — consume remaining bytes (arrow keys, etc.) - int next = passTerminal.reader().read(50); + int next = passTerminal.reader().read(); if (next == 91) { // '[' — arrow key sequence - passTerminal.reader().read(50); // consume 'A'/'B'/'C'/'D' + passTerminal.reader().read(); // consume 'A'/'B'/'C'/'D' } continue; } diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/MinecraftLib.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/MinecraftLib.java index 9593357..8242c16 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/MinecraftLib.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/MinecraftLib.java @@ -6,10 +6,14 @@ import me.sashegdev.zernmc.launcher.minecraft.installer.NeoForgeInstaller; import me.sashegdev.zernmc.launcher.minecraft.installer.VersionInstaller; import me.sashegdev.zernmc.launcher.minecraft.launch.LaunchCommandBuilder; import me.sashegdev.zernmc.launcher.minecraft.model.LaunchOptions; +import me.sashegdev.zernmc.launcher.ui.jfx.JFXLauncher; import me.sashegdev.zernmc.launcher.utils.ConsoleUtils; import me.sashegdev.zernmc.launcher.utils.ZAnsi; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; @@ -114,15 +118,43 @@ public class MinecraftLib { ProcessBuilder pb = new ProcessBuilder(command); pb.directory(instance.getPath().toFile()); - pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); - pb.redirectError(ProcessBuilder.Redirect.INHERIT); - pb.redirectInput(ProcessBuilder.Redirect.INHERIT); System.out.println(ZAnsi.brightGreen("\nЗапускаем Minecraft...\n")); ConsoleUtils.clearScreen(); Process process = pb.start(); + + // Capture output + Thread outThread = new Thread(() -> { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + JFXLauncher.appendGameLog(line); + } + } catch (Exception e) { + JFXLauncher.appendGameLog("[Ошибка чтения вывода: " + e.getMessage() + "]"); + } + }); + outThread.setDaemon(true); + outThread.start(); + + // Capture errors + Thread errThread = new Thread(() -> { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { + String line; + while ((line = reader.readLine()) != null) { + JFXLauncher.appendGameLog("[ERR] " + line); + } + } catch (Exception e) { + JFXLauncher.appendGameLog("[Ошибка чтения ошибок: " + e.getMessage() + "]"); + } + }); + errThread.setDaemon(true); + errThread.start(); + int exitCode = process.waitFor(); + outThread.join(1000); + errThread.join(1000); System.out.println(ZAnsi.yellow("\nMinecraft завершился с кодом: " + exitCode)); } diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/launch/VersionManifest.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/launch/VersionManifest.java index 2fdea9c..74debf6 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/launch/VersionManifest.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/launch/VersionManifest.java @@ -99,8 +99,17 @@ public class VersionManifest { if (rule.has("features")) { JSONObject features = rule.getJSONObject("features"); for (String key : features.keySet()) { - if (key.startsWith("is_demo_user") || key.startsWith("has_custom_resolution")) continue; + if (key.startsWith("has_custom_resolution")) { + continue; // Лаунчер сам обрабатывает разрешение + } + if (key.startsWith("is_demo_user")) { + // Лаунчер не использует demo режим, считаем фичу false + matches = false; + break; + } + // Неизвестная фича — считаем false matches = false; + break; } } diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/ArrowMenu.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/ArrowMenu.java index 1a1720c..eaf4eb5 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/ArrowMenu.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/ArrowMenu.java @@ -48,9 +48,9 @@ public class ArrowMenu { return selected; } else if (key == 27) { // Esc or arrow escape seq - int next = terminal.reader().read(50); + int next = terminal.reader().read(); if (next == 91) { // '[' — start of arrow escape sequence - int arrow = terminal.reader().read(50); + int arrow = terminal.reader().read(); if (arrow == 65) { // 'A' — Up arrow selected = (selected - 1 + options.size()) % options.size(); } else if (arrow == 66) { // 'B' — Down arrow diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java index 1b60d4b..496fbd8 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java @@ -16,6 +16,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executors; @@ -30,8 +33,21 @@ public class JFXLauncher extends Application { private final Gson gson = new Gson(); private HttpServer server; private StringBuilder logBuffer = new StringBuilder(); + private static StringBuilder gameLogBuffer = new StringBuilder(); private Stage mainStage; + public static void appendGameLog(String log) { + synchronized (gameLogBuffer) { + gameLogBuffer.append(log).append("\n"); + } + } + + public static String getGameLogs() { + synchronized (gameLogBuffer) { + return gameLogBuffer.toString(); + } + } + public static void main(String[] args) { launch(args); } @@ -49,12 +65,17 @@ public class JFXLauncher extends Application { engine.setJavaScriptEnabled(true); engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> { + log("[UI] Load state: " + oldState + " -> " + newState); if (newState == Worker.State.SUCCEEDED) { log("Страница загружена"); + } else if (newState == Worker.State.FAILED) { + log("[UI] Load FAILED: " + engine.getLoadWorker().getException()); } }); + + engine.setOnAlert(e -> log("[UI] Alert: " + e.getData())); - String url = "http://localhost:" + PORT + "/ui/"; + String url = "http://localhost:" + PORT + "/assets/ui/index.html"; engine.load(url); stage.setTitle(APP_TITLE); @@ -86,8 +107,9 @@ public class JFXLauncher extends Application { server.createContext("/api/launch", this::handleLaunch); server.createContext("/api/install", this::handleInstall); server.createContext("/api/logs", this::handleLogs); + server.createContext("/api/game-logs", this::handleGameLogs); server.createContext("/api/exit", this::handleExit); - server.createContext("/ui/", this::handleStatic); + server.createContext("/assets/", this::handleStatic); server.setExecutor(Executors.newCachedThreadPool()); server.start(); @@ -134,6 +156,8 @@ public class JFXLauncher extends Application { Map data = new HashMap<>(); data.put("username", api.getCurrentUsername()); data.put("passActive", AuthManager.hasActivePass()); + data.put("role", AuthManager.getRole()); + data.put("roleName", AuthManager.getRoleName()); sendJson(exchange, Map.of("success", true, "data", data)); } catch (Exception e) { sendJson(exchange, Map.of("success", false, "error", e.getMessage())); @@ -218,6 +242,10 @@ public class JFXLauncher extends Application { sendJson(exchange, Map.of("success", true, "data", logBuffer.toString())); } + private void handleGameLogs(HttpExchange exchange) { + sendJson(exchange, Map.of("success", true, "data", getGameLogs())); + } + private void handleExit(HttpExchange exchange) { log("Выход..."); if (mainStage != null) mainStage.close(); @@ -227,23 +255,29 @@ public class JFXLauncher extends Application { private void handleStatic(HttpExchange exchange) { try { String path = exchange.getRequestURI().getPath(); - if (path.equals("/ui/") || path.equals("/ui")) path = "/ui/index.html"; + log("[UI] Request: " + path); - var resource = JFXLauncher.class.getResource(path); - if (resource == null) { + String relativePath = path.startsWith("/") ? path.substring(1) : path; + Path file = Paths.get(relativePath).toAbsolutePath(); + + if (!Files.exists(file)) { + log("[UI] File not found: " + file); exchange.sendResponseHeaders(404, 0); exchange.close(); return; } - byte[] content = resource.openStream().readAllBytes(); + byte[] content = Files.readAllBytes(file); + log("[UI] Loaded " + content.length + " bytes: " + path); String ct = getContentType(path); exchange.getResponseHeaders().set("Content-Type", ct); exchange.sendResponseHeaders(200, content.length); exchange.getResponseBody().write(content); exchange.close(); - } catch (Exception ignored) {} + } catch (Exception e) { + log("[UI] Error serving: " + e.getMessage()); + } } private String getContentType(String path) { diff --git a/launcher/launcher/src/resources/ui/index.html b/launcher/launcher/src/resources/ui/index.html index daf5136..13583c7 100644 --- a/launcher/launcher/src/resources/ui/index.html +++ b/launcher/launcher/src/resources/ui/index.html @@ -30,6 +30,7 @@ diff --git a/launcher/launcher/src/resources/ui/launcher.js b/launcher/launcher/src/resources/ui/launcher.js index e4236d7..42f20d2 100644 --- a/launcher/launcher/src/resources/ui/launcher.js +++ b/launcher/launcher/src/resources/ui/launcher.js @@ -70,6 +70,11 @@ async function loadAccountInfo() { const statusEl = document.getElementById('account-status'); statusEl.textContent = result.data.passActive ? 'PRO' : 'FREE'; statusEl.className = 'badge ' + (result.data.passActive ? 'active' : 'inactive'); + + const roleEl = document.getElementById('account-role'); + if (roleEl && result.data.roleName) { + roleEl.textContent = result.data.roleName; + } } else { showLoginScreen(); } @@ -239,8 +244,41 @@ document.addEventListener('DOMContentLoaded', async () => { showMainScreen(); await loadInstances(); } + + // Start polling for server logs + startLogPolling(); }); +let lastLogLength = 0; +let lastGameLogLength = 0; +function startLogPolling() { + setInterval(async () => { + // Launcher logs + const result = await apiCall('/logs'); + if (result.success && result.data && result.data.length > lastLogLength) { + const newLogs = result.data.substring(lastLogLength); + const lines = newLogs.split('\n').filter(l => l.trim()); + lines.forEach(line => { + if (line.includes('[JFX]')) { + log(line.replace('[JFX] ', ''), 'info'); + } + }); + lastLogLength = result.data.length; + } + + // Game logs + const gameResult = await apiCall('/game-logs'); + if (gameResult.success && gameResult.data && gameResult.data.length > lastGameLogLength) { + const newLogs = gameResult.data.substring(lastGameLogLength); + const lines = newLogs.split('\n').filter(l => l.trim()); + lines.forEach(line => { + log('[GAME] ' + line, 'info'); + }); + lastGameLogLength = gameResult.data.length; + } + }, 2000); +} + // ============ Form Handlers ============ document.getElementById('login-form').addEventListener('submit', async (e) => { diff --git a/launcher/launcher/src/resources/ui/style.css b/launcher/launcher/src/resources/ui/style.css index 40b3776..481035c 100644 --- a/launcher/launcher/src/resources/ui/style.css +++ b/launcher/launcher/src/resources/ui/style.css @@ -194,6 +194,11 @@ input::placeholder { color: var(--error); } +.role-badge { + background: rgba(99, 102, 241, 0.2); + color: #818cf8; +} + /* Main Content */ .main-content { flex: 1; diff --git a/launcher/pom.xml b/launcher/pom.xml index 9cfb6e5..ea9f5d6 100644 --- a/launcher/pom.xml +++ b/launcher/pom.xml @@ -6,7 +6,7 @@ 4.0.0 me.sashegdev ZernMCLauncher - 1.0.8 + 1.0.9 pom ZernMC Launcher Parent diff --git a/server/main.py b/server/main.py index 3894c2d..83faa59 100644 --- a/server/main.py +++ b/server/main.py @@ -150,6 +150,11 @@ async def lifespan(app: FastAPI): # Scan launcher versions and generate meta logger.info("Scanning launcher versions...") + + # Extract new format ZIPs to versions directory + logger.info("Extracting new format versions...") + extract_new_format_versions() + launcher_versions = get_launcher_versions() if launcher_versions: latest = launcher_versions[0] @@ -773,7 +778,12 @@ async def get_pack_file(pack_name: str, file_path: str, request: Request): # ====================== ЭНДПОИНТЫ ДЛЯ ЛАУНЧЕРА ====================== def get_current_launcher_version() -> str: - """Get current launcher version from build.version file""" + """Get current launcher version from meta system (new format) or build.version (legacy)""" + versions = get_launcher_versions() + if versions: + return versions[0]["meta"]["version"] + + # Fallback to build.version for legacy version_file = BUILDS_DIR / "build.version" if version_file.exists(): return version_file.read_text().strip() @@ -892,6 +902,35 @@ def get_launcher_version_meta(version: str) -> Optional[dict]: return scan_launcher_version(version) +def extract_new_format_versions(): + """Extract new format ZIPs to versions directory""" + VERSIONS_DIR.mkdir(exist_ok=True) + + # Find all ZernMC-win-*.zip files + new_format_zips = list(BUILDS_DIR.glob("ZernMC-win-*.zip")) + + for zip_file in new_format_zips: + version = zip_file.stem.replace("ZernMC-win-", "") + extract_dir = VERSIONS_DIR / version + + # Skip if already extracted and meta exists + if extract_dir.exists() and (extract_dir / "meta.json").exists(): + logger.debug(f"Version {version} already extracted") + continue + + logger.info(f"Extracting {zip_file.name} to versions/{version}/...") + + try: + import zipfile + with zipfile.ZipFile(zip_file, 'r') as zf: + # Extract all files + zf.extractall(extract_dir) + + logger.info(f"Extracted {zip_file.name} successfully") + except Exception as e: + logger.error(f"Failed to extract {zip_file.name}: {e}") + + # ====================== END ЛАУНЧЕР МЕТА СИСТЕМА ====================== @@ -1008,14 +1047,17 @@ async def get_launcher_version(): @app.get("/launcher/download/jar") async def download_launcher_jar(): """Download launcher JAR file""" - file_path = BUILDS_DIR / "ZernMCLauncher.jar" + # Prefer new shaded JAR, fallback to old + file_path = BUILDS_DIR / "zernmclauncher.jar" + if not file_path.exists(): + file_path = BUILDS_DIR / "ZernMCLauncher.jar" if not file_path.exists(): raise HTTPException(404, "JAR file not found") return FileResponse( path=file_path, - filename="ZernMCLauncher.jar", + filename="zernmclauncher.jar", media_type="application/java-archive" ) @@ -1114,7 +1156,7 @@ async def get_launcher_meta_list(): @app.get("/launcher/meta/{version}") -async def get_launcher_version_meta(version: str): +async def get_launcher_version_meta_handler(version: str): """Get meta for specific launcher version""" meta = get_launcher_version_meta(version) if not meta: