From e21fd922ab48a52b935f6f4c3d8bf4fe4bf7647c Mon Sep 17 00:00:00 2001 From: Sashegdev Date: Sun, 5 Apr 2026 16:18:39 +0000 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=8B=D1=82=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B7=D0=B0=D1=81=D1=82=D0=B0=D0=B2=D0=B8=D1=82=D1=8C=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D1=82=D1=8C=20Forge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../minecraft/installer/ForgeInstaller.java | 225 +++++++++++++--- .../launch/LaunchCommandBuilder.java | 251 ++++++++++++++---- .../zernmc/launcher/utils/ProgressBar.java | 11 + 3 files changed, 410 insertions(+), 77 deletions(-) diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java index 412b652..2330fd6 100644 --- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java +++ b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java @@ -3,7 +3,7 @@ package me.sashegdev.zernmc.launcher.minecraft.installer; import me.sashegdev.zernmc.launcher.minecraft.Instance; import me.sashegdev.zernmc.launcher.utils.ProgressBar; import me.sashegdev.zernmc.launcher.utils.ZAnsi; -import java.io.IOException; +import java.io.*; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -11,6 +11,8 @@ import java.net.http.HttpResponse; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.util.HashMap; +import java.util.Map; public class ForgeInstaller { @@ -30,56 +32,55 @@ public class ForgeInstaller { System.out.println(ZAnsi.cyan("Установка базовой версии Minecraft " + mcVersion + "...")); VersionInstaller vanillaInstaller = new VersionInstaller(instance.getPath()); - String assetIndex = vanillaInstaller.install(mcVersion); // ← теперь возвращает String + String assetIndex = vanillaInstaller.install(mcVersion); if (assetIndex == null || assetIndex.isEmpty()) { System.out.println(ZAnsi.brightRed("Не удалось установить базовую версию Minecraft")); return false; } - // Сохраняем assetIndex (очень важно!) instance.setAssetIndex(assetIndex); // Шаг 2: Создаём launcher_profiles.json createLauncherProfile(); - // Шаг 3: Скачиваем и запускаем Forge Installer + // Шаг 3: Скачиваем Forge Installer с прогресс-баром String installerUrl = "https://maven.minecraftforge.net/net/minecraftforge/forge/" + mcVersion + "-" + forgeVersion + "/forge-" + mcVersion + "-" + forgeVersion + "-installer.jar"; Path installerJar = instance.getPath().resolve("forge-installer.jar"); - ProgressBar.show("Скачивание Forge Installer", 0, 100, "%"); - downloadFile(installerUrl, installerJar); - ProgressBar.finish("Forge Installer скачан"); + System.out.println(ZAnsi.cyan("Скачивание Forge Installer...")); + downloadFileWithProgress(installerUrl, installerJar); + // Шаг 4: Запускаем Forge Installer и показываем его вывод System.out.println(ZAnsi.cyan("Запуск Forge Installer...")); - ProcessBuilder pb = new ProcessBuilder( - "java", - "-jar", - installerJar.toAbsolutePath().toString(), - "--installClient" - ); - pb.directory(instance.getPath().toFile()); - pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); - pb.redirectError(ProcessBuilder.Redirect.INHERIT); + System.out.println(ZAnsi.yellow("Это может занять несколько минут. Пожалуйста, подождите...\n")); + + boolean success = runForgeInstaller(installerJar); - Process process = pb.start(); - int exitCode = process.waitFor(); - - if (exitCode != 0) { - System.out.println(ZAnsi.brightRed("Forge Installer завершился с ошибкой (код " + exitCode + ")")); + // После успешной установки Forge, но перед сохранением метаданных + if (success) { + // Докачиваем пропущенные библиотеки + try { + downloadMissingLibraries(mcVersion, forgeVersion); + } catch (Exception e) { + System.out.println(ZAnsi.yellow("Предупреждение: не удалось докачать некоторые библиотеки: " + e.getMessage())); + } + + System.out.println(ZAnsi.brightGreen("\nForge " + forgeVersion + " успешно установлен!")); + instance.setMinecraftVersion(mcVersion); + instance.setLoaderType("forge"); + instance.setLoaderVersion(forgeVersion); + + // Очищаем временный файл установщика + Files.deleteIfExists(installerJar); + return true; + } else { + System.out.println(ZAnsi.brightRed("\nОшибка при установке Forge!")); return false; } - - System.out.println(ZAnsi.brightGreen("Forge " + forgeVersion + " успешно установлен!")); - - instance.setMinecraftVersion(mcVersion); - instance.setLoaderType("forge"); - instance.setLoaderVersion(forgeVersion); - - return true; } private void createLauncherProfile() throws IOException { @@ -96,14 +97,174 @@ public class ForgeInstaller { System.out.println(ZAnsi.yellow("Создан launcher_profiles.json")); } - private void downloadFile(String url, Path target) throws Exception { + private void downloadFileWithProgress(String url, Path target) throws Exception { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .GET() .build(); - HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofFile(target)); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); + if (response.statusCode() != 200) { - throw new IOException("Не удалось скачать Forge installer (HTTP " + response.statusCode() + ")"); + throw new IOException("HTTP " + response.statusCode()); + } + + long contentLength = response.headers().firstValueAsLong("Content-Length").orElse(-1); + + try (InputStream in = response.body(); + FileOutputStream out = new FileOutputStream(target.toFile())) { + + byte[] buffer = new byte[8192]; + int bytesRead; + long totalRead = 0; + int lastPercent = -1; + + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + totalRead += bytesRead; + + if (contentLength > 0) { + int percent = (int) ((totalRead * 100) / contentLength); + if (percent != lastPercent) { + String downloaded = ProgressBar.formatBytes(totalRead); + String total = ProgressBar.formatBytes(contentLength); + ProgressBar.show("Forge Installer", percent, 100, "% (" + downloaded + "/" + total + ")"); + lastPercent = percent; + } + } else { + // Если размер неизвестен, показываем анимацию + char[] spinner = {'|', '/', '-', '\\'}; + int idx = (int) (totalRead / 1024) % 4; + System.out.print("\rСкачивание Forge Installer: " + ProgressBar.formatBytes(totalRead) + " " + spinner[idx]); + } + } + } + + ProgressBar.finish("Forge Installer (" + ProgressBar.formatBytes(Files.size(target)) + ")"); + } + + private boolean runForgeInstaller(Path installerJar) throws IOException, InterruptedException { + // Пробуем до 3 раз с разными опциями + int maxRetries = 3; + int attempt = 1; + + while (attempt <= maxRetries) { + System.out.println(ZAnsi.cyan("Попытка " + attempt + " из " + maxRetries)); + + ProcessBuilder pb = new ProcessBuilder( + "java", + "-jar", + installerJar.toAbsolutePath().toString(), + "--installClient" + ); + + // Добавляем JVM аргументы для увеличения таймаутов + pb.environment().put("JAVA_OPTS", "-Dhttp.connectionTimeout=60000 -Dhttp.socketTimeout=60000"); + + pb.directory(instance.getPath().toFile()); + pb.redirectErrorStream(true); + + Process process = pb.start(); + + // Читаем вывод в реальном времени + StringBuilder output = new StringBuilder(); + boolean hasErrors = false; + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append("\n"); + + // Форматируем вывод Forge Installer + if (line.contains("Downloading") || line.contains("Extracting")) { + System.out.println(ZAnsi.blue(" -> " + line)); + } else if (line.contains("SUCCESS") || line.contains("successfully")) { + System.out.println(ZAnsi.brightGreen(" + " + line)); + } else if (line.contains("WARNING") || line.contains("warning")) { + System.out.println(ZAnsi.yellow(" ! " + line)); + } else if (line.contains("ERROR") || line.contains("error") || line.contains("failed") || line.contains("timed out")) { + System.out.println(ZAnsi.brightRed(" X " + line)); + if (line.contains("timed out") || line.contains("failed to download")) { + hasErrors = true; + } + } else if (!line.isBlank()) { + System.out.println(" " + line); + } + } + } + + int exitCode = process.waitFor(); + + // Если успешно или нет ошибок скачивания + if (exitCode == 0 && !hasErrors) { + return true; + } + + // Если ошибка и это не последняя попытка + if (attempt < maxRetries) { + System.out.println(ZAnsi.yellow("Ошибка при установке. Повторная попытка через 5 секунд...")); + Thread.sleep(5000); + + // Очищаем временные файлы перед повтором + Path librariesDir = instance.getPath().resolve("libraries"); + if (Files.exists(librariesDir)) { + // Удаляем только частично скачанные библиотеки Forge + try (var stream = Files.walk(librariesDir)) { + stream.filter(p -> p.toString().contains("asm") && p.toString().endsWith(".jar")) + .forEach(p -> { + try { Files.deleteIfExists(p); } + catch (IOException e) { /* ignore */ } + }); + } + } + } else { + System.out.println(ZAnsi.brightRed("Forge Installer завершился с кодом ошибки: " + exitCode)); + + // Показываем возможное решение + if (output.toString().contains("timed out")) { + System.out.println(ZAnsi.yellow("\nВозможные решения:")); + System.out.println(ZAnsi.yellow("1. Проверьте интернет-соединение")); + System.out.println(ZAnsi.yellow("2. Запустите лаунчер от имени администратора")); + System.out.println(ZAnsi.yellow("3. Временно отключите антивирус/брандмауэр")); + System.out.println(ZAnsi.yellow("4. Попробуйте установить другую версию Forge")); + } + } + + attempt++; + } + + return false; + } + + private void downloadMissingLibraries(String mcVersion, String forgeVersion) throws Exception { + System.out.println(ZAnsi.cyan("Проверка и докачка отсутствующих библиотек...")); + + // Список проблемных библиотек и их альтернативные URL + Map alternativeUrls = new HashMap<>(); + alternativeUrls.put("org/ow2/asm/asm/9.6/asm-9.6.jar", + "https://repo1.maven.org/maven2/org/ow2/asm/asm/9.6/asm-9.6.jar"); + alternativeUrls.put("org/ow2/asm/asm/9.6/asm-9.6.jar", + "https://mirrors.huaweicloud.com/repository/maven/org/ow2/asm/asm/9.6/asm-9.6.jar"); + + Path librariesDir = instance.getPath().resolve("libraries"); + + for (Map.Entry entry : alternativeUrls.entrySet()) { + Path target = librariesDir.resolve(entry.getKey()); + if (!Files.exists(target)) { + Files.createDirectories(target.getParent()); + System.out.println(ZAnsi.yellow("Докачка: " + target.getFileName())); + + for (int attempt = 1; attempt <= 3; attempt++) { + try { + downloadFileWithProgress(entry.getValue(), target); + break; + } catch (Exception e) { + if (attempt == 3) throw e; + System.out.println(ZAnsi.yellow("Повторная попытка " + attempt + "/3...")); + Thread.sleep(2000); + } + } + } } } } \ No newline at end of file diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/launch/LaunchCommandBuilder.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/launch/LaunchCommandBuilder.java index df3a677..b6e3d87 100644 --- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/launch/LaunchCommandBuilder.java +++ b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/launch/LaunchCommandBuilder.java @@ -9,9 +9,6 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -/** - * Генерирует полную команду запуска Minecraft (Vanilla / Fabric / Forge) - */ public class LaunchCommandBuilder { private final Instance instance; @@ -34,37 +31,52 @@ public class LaunchCommandBuilder { // 3. Natives Path nativesDir = instance.getPath().resolve("natives"); + if (!Files.exists(nativesDir)) { + Files.createDirectories(nativesDir); + } command.add("-Djava.library.path=" + nativesDir.toAbsolutePath()); - // 4. Classpath - String classpath = buildClasspath(); - command.add("-cp"); - command.add(classpath); - - // 5. Главный класс - String mainClass = getMainClass(); - command.add(mainClass); - - // 6. Аргументы Minecraft - command.addAll(getMinecraftArguments(options)); + String loaderType = instance.getLoaderType().toLowerCase(); + + if ("forge".equals(loaderType)) { + // Forge требует особого порядка аргументов + command.addAll(getForgeJvmArguments()); + + // Forge специфичный classpath + command.add("-cp"); + command.add(buildForgeClasspath()); + + // Главный класс для Forge + command.add("cpw.mods.modlauncher.Launcher"); + + // Аргументы Forge + command.addAll(getForgeArguments(options)); + } else { + // Стандартный classpath для vanilla/fabric + command.add("-cp"); + command.add(buildClasspath()); + + // Главный класс + command.add(getMainClass()); + + // Стандартные аргументы Minecraft + command.addAll(getMinecraftArguments(options)); + } return command; } private String getJavaPath() { - // Пока берём системную java. Позже можно добавить выбор из ~/.zernmc/jre/ return "java"; } private List getJvmArguments(LaunchOptions options) { List jvmArgs = new ArrayList<>(); - // Выделенная память - int ramMB = options.getMaxMemory() > 0 ? options.getMaxMemory() : 2048; + int ramMB = options.getMaxMemory() > 0 ? options.getMaxMemory() : 4096; jvmArgs.add("-Xmx" + ramMB + "M"); jvmArgs.add("-Xms" + Math.max(512, ramMB / 2) + "M"); - // Стандартные оптимизации jvmArgs.add("-XX:+UseG1GC"); jvmArgs.add("-XX:+UnlockExperimentalVMOptions"); jvmArgs.add("-XX:G1NewSizePercent=20"); @@ -72,12 +84,8 @@ public class LaunchCommandBuilder { jvmArgs.add("-XX:MaxGCPauseMillis=50"); jvmArgs.add("-XX:G1HeapRegionSize=32M"); - // Дополнительные JVM аргументы из настроек пользователя - if (options.getExtraJvmArgs() != null && !options.getExtraJvmArgs().isEmpty()) { - jvmArgs.addAll(options.getExtraJvmArgs()); - } - String loaderType = instance.getLoaderType().toLowerCase(); + if ("fabric".equals(loaderType)) { jvmArgs.add("--add-modules=ALL-MODULE-PATH"); jvmArgs.add("--add-opens=java.base/java.io=ALL-UNNAMED"); @@ -91,35 +99,63 @@ public class LaunchCommandBuilder { jvmArgs.add("--add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED"); } + if (options.getExtraJvmArgs() != null && !options.getExtraJvmArgs().isEmpty()) { + jvmArgs.addAll(options.getExtraJvmArgs()); + } + + return jvmArgs; + } + + private List getForgeJvmArguments() { + List jvmArgs = new ArrayList<>(); + + // Критические аргументы для Forge + jvmArgs.add("--add-modules=ALL-MODULE-PATH"); + jvmArgs.add("--add-opens=java.base/java.util.jar=ALL-UNNAMED"); + jvmArgs.add("--add-opens=java.base/java.lang.invoke=ALL-UNNAMED"); + jvmArgs.add("--add-opens=java.base/java.lang.reflect=ALL-UNNAMED"); + jvmArgs.add("--add-opens=java.base/java.io=ALL-UNNAMED"); + jvmArgs.add("--add-opens=java.base/java.nio=ALL-UNNAMED"); + jvmArgs.add("--add-opens=java.base/java.net=ALL-UNNAMED"); + jvmArgs.add("--add-opens=java.base/java.util=ALL-UNNAMED"); + jvmArgs.add("--add-opens=java.base/sun.nio.ch=ALL-UNNAMED"); + jvmArgs.add("--add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED"); + + // Forge специфичные свойства + jvmArgs.add("-Dforge.logging.console.level=debug"); + jvmArgs.add("-Dforge.logging.mojang.level=info"); + jvmArgs.add("-DignoreList=bootstraplauncher,securejarhandler,asm-commons,asm-util,asm-analysis,asm-tree,asm,JarJarFileSystems,client-extra,fmlcore,javafmllanguage,lowcodelanguage,mclanguage,forge-"); + jvmArgs.add("-DmergeModules=jna-5.10.0.jar,jna-platform-5.10.0.jar"); + return jvmArgs; } private String buildClasspath() throws Exception { List paths = new ArrayList<>(); - - //String loaderType = instance.getLoaderType().toLowerCase(); - String versionId = getVersionId(); - + + String versionId = getVersionId(); // ← используем getVersionId() + + // Добавляем основной jar Path versionJar = instance.getPath() .resolve("versions") .resolve(versionId) .resolve(versionId + ".jar"); - + if (Files.exists(versionJar)) { paths.add(versionJar.toAbsolutePath().toString()); } else { - Path altVersionJar = instance.getPath() + // Fallback на vanilla версию + String mcVersion = instance.getMinecraftVersion(); + Path fallbackJar = instance.getPath() .resolve("versions") - .resolve(instance.getMinecraftVersion()) - .resolve(instance.getMinecraftVersion() + ".jar"); - - if (Files.exists(altVersionJar)) { - paths.add(altVersionJar.toAbsolutePath().toString()); - } else { - System.err.println(ZAnsi.yellow("Warning: Vanilla Minecraft jar not found at: " + versionJar)); + .resolve(mcVersion) + .resolve(mcVersion + ".jar"); + if (Files.exists(fallbackJar)) { + paths.add(fallbackJar.toAbsolutePath().toString()); } } - + + // Все библиотеки Path librariesDir = instance.getPath().resolve("libraries"); if (Files.exists(librariesDir)) { try (var stream = Files.walk(librariesDir)) { @@ -128,6 +164,84 @@ public class LaunchCommandBuilder { .forEach(paths::add); } } + + String separator = System.getProperty("os.name").toLowerCase().contains("win") ? ";" : ":"; + return String.join(separator, paths); + } + + private String buildForgeClasspath() throws Exception { + List paths = new ArrayList<>(); + + String versionId = getVersionId(); // ← используем getVersionId() + String mcVersion = instance.getMinecraftVersion(); + String forgeVersion = instance.getLoaderVersion(); + + // 1. Сначала добавляем все библиотеки из libraries + Path librariesDir = instance.getPath().resolve("libraries"); + if (Files.exists(librariesDir)) { + try (var stream = Files.walk(librariesDir)) { + stream.filter(p -> p.toString().endsWith(".jar")) + .map(p -> p.toAbsolutePath().toString()) + .forEach(paths::add); + } + } + + // 2. Добавляем jar версии (используя versionId) + Path versionJar = instance.getPath() + .resolve("versions") + .resolve(versionId) + .resolve(versionId + ".jar"); + if (Files.exists(versionJar)) { + paths.add(0, versionJar.toAbsolutePath().toString()); + } else { + // Fallback на vanilla jar + Path vanillaJar = instance.getPath() + .resolve("versions") + .resolve(mcVersion) + .resolve(mcVersion + ".jar"); + if (Files.exists(vanillaJar)) { + paths.add(0, vanillaJar.toAbsolutePath().toString()); + } + } + + // 3. Добавляем Forge universal jar + Path forgeUniversal = instance.getPath() + .resolve("libraries") + .resolve("net") + .resolve("minecraftforge") + .resolve("forge") + .resolve(mcVersion + "-" + forgeVersion) + .resolve("forge-" + mcVersion + "-" + forgeVersion + "-universal.jar"); + if (Files.exists(forgeUniversal)) { + paths.add(forgeUniversal.toAbsolutePath().toString()); + } + + // 4. Добавляем Forge client jar + Path forgeClient = instance.getPath() + .resolve("libraries") + .resolve("net") + .resolve("minecraftforge") + .resolve("forge") + .resolve(mcVersion + "-" + forgeVersion) + .resolve("forge-" + mcVersion + "-" + forgeVersion + "-client.jar"); + if (Files.exists(forgeClient)) { + paths.add(forgeClient.toAbsolutePath().toString()); + } + + // 5. Добавляем fmlcore и другие Forge модули + String[] forgeModules = {"fmlcore", "javafmllanguage", "lowcodelanguage", "mclanguage"}; + for (String module : forgeModules) { + Path modulePath = instance.getPath() + .resolve("libraries") + .resolve("net") + .resolve("minecraftforge") + .resolve(module) + .resolve(mcVersion + "-" + forgeVersion) + .resolve(module + "-" + mcVersion + "-" + forgeVersion + ".jar"); + if (Files.exists(modulePath)) { + paths.add(modulePath.toAbsolutePath().toString()); + } + } String separator = System.getProperty("os.name").toLowerCase().contains("win") ? ";" : ":"; return String.join(separator, paths); @@ -137,12 +251,10 @@ public class LaunchCommandBuilder { String loaderType = instance.getLoaderType().toLowerCase(); if ("fabric".equals(loaderType)) { - // Fabric 0.14+ использует KnotClient return "net.fabricmc.loader.impl.launch.knot.KnotClient"; } else if ("forge".equals(loaderType)) { - // Forge 1.20.1 использует ClientModLoader - return "net.minecraftforge.client.loading.ClientModLoader"; + return "cpw.mods.modlauncher.Launcher"; } else { return "net.minecraft.client.main.Main"; @@ -168,15 +280,14 @@ public class LaunchCommandBuilder { args.add(options.getUsername() != null ? options.getUsername() : "Player"); args.add("--accessToken"); - args.add("0"); // потом токен от блядкого сервера + args.add(options.getAccessToken() != null ? options.getAccessToken() : "0"); args.add("--uuid"); - args.add("00000000-0000-0000-0000-000000000000"); // тоже потом от блядкого сервера + args.add(options.getUuid() != null ? options.getUuid() : "00000000-0000-0000-0000-000000000000"); args.add("--userType"); args.add("legacy"); - // Дополнительные параметры if (options.getWidth() > 0) { args.add("--width"); args.add(String.valueOf(options.getWidth())); @@ -189,6 +300,56 @@ public class LaunchCommandBuilder { return args; } + private List getForgeArguments(LaunchOptions options) { + List args = new ArrayList<>(); + + // Forge требует специфические аргументы в правильном порядке + args.add("--launchTarget"); + args.add("forgeclient"); + + args.add("--fml.forgeVersion"); + args.add(instance.getLoaderVersion()); + + args.add("--fml.mcVersion"); + args.add(instance.getMinecraftVersion()); + + args.add("--fml.forgeGroup"); + args.add("net.minecraftforge"); + + // Добавляем стандартные аргументы Minecraft (Forge их тоже принимает) + args.add("--gameDir"); + args.add(instance.getPath().toAbsolutePath().toString()); + + args.add("--assetsDir"); + args.add(instance.getPath().resolve("assets").toAbsolutePath().toString()); + + args.add("--assetIndex"); + args.add(instance.getAssetIndex()); + + args.add("--username"); + args.add(options.getUsername() != null ? options.getUsername() : "Player"); + + args.add("--accessToken"); + args.add(options.getAccessToken() != null ? options.getAccessToken() : "0"); + + args.add("--uuid"); + args.add(options.getUuid() != null ? options.getUuid() : "00000000-0000-0000-0000-000000000000"); + + args.add("--userType"); + args.add("legacy"); + + if (options.getWidth() > 0) { + args.add("--width"); + args.add(String.valueOf(options.getWidth())); + } + if (options.getHeight() > 0) { + args.add("--height"); + args.add(String.valueOf(options.getHeight())); + } + + return args; + } + private String getVersionId() { String loaderType = instance.getLoaderType().toLowerCase(); String mcVersion = instance.getMinecraftVersion(); @@ -198,14 +359,14 @@ public class LaunchCommandBuilder { return mcVersion; } else if ("fabric".equals(loaderType)) { + // Fabric использует vanilla версию для jar файла return mcVersion; } else if ("forge".equals(loaderType)) { + // Forge создаёт свою версию в папке versions return mcVersion + "-forge-" + loaderVer; } return mcVersion; } - - } \ No newline at end of file diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ProgressBar.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ProgressBar.java index 3f35a24..d85bb2d 100644 --- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ProgressBar.java +++ b/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ProgressBar.java @@ -52,6 +52,17 @@ public class ProgressBar { System.out.flush(); } + public static void showAnimated(String label, long current, long total, String unit) { + if (total <= 0) { + // Анимация для неизвестного размера + char[] spinner = {'|', '/', '-', '\\'}; + int idx = (int) (current / 1024) % 4; + System.out.print("\r" + label + " [" + spinner[idx] + "] " + formatBytes(current)); + } else { + show(label, (int) ((current * 100) / total), 100, unit); + } + } + public static void finish(String message) { System.out.println("\r" + ZAnsi.brightGreen(message + " завершено ✓")); System.out.flush();