Попытка заставить работать Forge

This commit is contained in:
Sashegdev
2026-04-05 16:18:39 +00:00
parent b29222af68
commit e21fd922ab
3 changed files with 410 additions and 77 deletions
@@ -3,7 +3,7 @@ package me.sashegdev.zernmc.launcher.minecraft.installer;
import me.sashegdev.zernmc.launcher.minecraft.Instance; import me.sashegdev.zernmc.launcher.minecraft.Instance;
import me.sashegdev.zernmc.launcher.utils.ProgressBar; import me.sashegdev.zernmc.launcher.utils.ProgressBar;
import me.sashegdev.zernmc.launcher.utils.ZAnsi; import me.sashegdev.zernmc.launcher.utils.ZAnsi;
import java.io.IOException; import java.io.*;
import java.net.URI; import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
@@ -11,6 +11,8 @@ import java.net.http.HttpResponse;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.HashMap;
import java.util.Map;
public class ForgeInstaller { public class ForgeInstaller {
@@ -30,56 +32,55 @@ public class ForgeInstaller {
System.out.println(ZAnsi.cyan("Установка базовой версии Minecraft " + mcVersion + "...")); System.out.println(ZAnsi.cyan("Установка базовой версии Minecraft " + mcVersion + "..."));
VersionInstaller vanillaInstaller = new VersionInstaller(instance.getPath()); VersionInstaller vanillaInstaller = new VersionInstaller(instance.getPath());
String assetIndex = vanillaInstaller.install(mcVersion); // ← теперь возвращает String String assetIndex = vanillaInstaller.install(mcVersion);
if (assetIndex == null || assetIndex.isEmpty()) { if (assetIndex == null || assetIndex.isEmpty()) {
System.out.println(ZAnsi.brightRed("Не удалось установить базовую версию Minecraft")); System.out.println(ZAnsi.brightRed("Не удалось установить базовую версию Minecraft"));
return false; return false;
} }
// Сохраняем assetIndex (очень важно!)
instance.setAssetIndex(assetIndex); instance.setAssetIndex(assetIndex);
// Шаг 2: Создаём launcher_profiles.json // Шаг 2: Создаём launcher_profiles.json
createLauncherProfile(); createLauncherProfile();
// Шаг 3: Скачиваем и запускаем Forge Installer // Шаг 3: Скачиваем Forge Installer с прогресс-баром
String installerUrl = "https://maven.minecraftforge.net/net/minecraftforge/forge/" String installerUrl = "https://maven.minecraftforge.net/net/minecraftforge/forge/"
+ mcVersion + "-" + forgeVersion + mcVersion + "-" + forgeVersion
+ "/forge-" + mcVersion + "-" + forgeVersion + "-installer.jar"; + "/forge-" + mcVersion + "-" + forgeVersion + "-installer.jar";
Path installerJar = instance.getPath().resolve("forge-installer.jar"); Path installerJar = instance.getPath().resolve("forge-installer.jar");
ProgressBar.show("Скачивание Forge Installer", 0, 100, "%"); System.out.println(ZAnsi.cyan("Скачивание Forge Installer..."));
downloadFile(installerUrl, installerJar); downloadFileWithProgress(installerUrl, installerJar);
ProgressBar.finish("Forge Installer скачан");
// Шаг 4: Запускаем Forge Installer и показываем его вывод
System.out.println(ZAnsi.cyan("Запуск Forge Installer...")); System.out.println(ZAnsi.cyan("Запуск Forge Installer..."));
ProcessBuilder pb = new ProcessBuilder( System.out.println(ZAnsi.yellow("Это может занять несколько минут. Пожалуйста, подождите...\n"));
"java",
"-jar",
installerJar.toAbsolutePath().toString(),
"--installClient"
);
pb.directory(instance.getPath().toFile());
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
Process process = pb.start(); boolean success = runForgeInstaller(installerJar);
int exitCode = process.waitFor();
if (exitCode != 0) { // После успешной установки Forge, но перед сохранением метаданных
System.out.println(ZAnsi.brightRed("Forge Installer завершился с ошибкой (код " + exitCode + ")")); if (success) {
return false; // Докачиваем пропущенные библиотеки
try {
downloadMissingLibraries(mcVersion, forgeVersion);
} catch (Exception e) {
System.out.println(ZAnsi.yellow("Предупреждение: не удалось докачать некоторые библиотеки: " + e.getMessage()));
} }
System.out.println(ZAnsi.brightGreen("Forge " + forgeVersion + " успешно установлен!")); System.out.println(ZAnsi.brightGreen("\nForge " + forgeVersion + " успешно установлен!"));
instance.setMinecraftVersion(mcVersion); instance.setMinecraftVersion(mcVersion);
instance.setLoaderType("forge"); instance.setLoaderType("forge");
instance.setLoaderVersion(forgeVersion); instance.setLoaderVersion(forgeVersion);
// Очищаем временный файл установщика
Files.deleteIfExists(installerJar);
return true; return true;
} else {
System.out.println(ZAnsi.brightRed("\nОшибка при установке Forge!"));
return false;
}
} }
private void createLauncherProfile() throws IOException { private void createLauncherProfile() throws IOException {
@@ -96,14 +97,174 @@ public class ForgeInstaller {
System.out.println(ZAnsi.yellow("Создан launcher_profiles.json")); 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() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url)) .uri(URI.create(url))
.GET() .GET()
.build(); .build();
HttpResponse<Path> response = httpClient.send(request, HttpResponse.BodyHandlers.ofFile(target));
HttpResponse<InputStream> response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
if (response.statusCode() != 200) { 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<String, String> 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<String, String> 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);
}
}
}
} }
} }
} }
@@ -9,9 +9,6 @@ import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/**
* Генерирует полную команду запуска Minecraft (Vanilla / Fabric / Forge)
*/
public class LaunchCommandBuilder { public class LaunchCommandBuilder {
private final Instance instance; private final Instance instance;
@@ -34,37 +31,52 @@ public class LaunchCommandBuilder {
// 3. Natives // 3. Natives
Path nativesDir = instance.getPath().resolve("natives"); Path nativesDir = instance.getPath().resolve("natives");
if (!Files.exists(nativesDir)) {
Files.createDirectories(nativesDir);
}
command.add("-Djava.library.path=" + nativesDir.toAbsolutePath()); command.add("-Djava.library.path=" + nativesDir.toAbsolutePath());
// 4. Classpath String loaderType = instance.getLoaderType().toLowerCase();
String classpath = buildClasspath();
if ("forge".equals(loaderType)) {
// Forge требует особого порядка аргументов
command.addAll(getForgeJvmArguments());
// Forge специфичный classpath
command.add("-cp"); command.add("-cp");
command.add(classpath); command.add(buildForgeClasspath());
// 5. Главный класс // Главный класс для Forge
String mainClass = getMainClass(); command.add("cpw.mods.modlauncher.Launcher");
command.add(mainClass);
// 6. Аргументы Minecraft // Аргументы Forge
command.addAll(getForgeArguments(options));
} else {
// Стандартный classpath для vanilla/fabric
command.add("-cp");
command.add(buildClasspath());
// Главный класс
command.add(getMainClass());
// Стандартные аргументы Minecraft
command.addAll(getMinecraftArguments(options)); command.addAll(getMinecraftArguments(options));
}
return command; return command;
} }
private String getJavaPath() { private String getJavaPath() {
// Пока берём системную java. Позже можно добавить выбор из ~/.zernmc/jre/
return "java"; return "java";
} }
private List<String> getJvmArguments(LaunchOptions options) { private List<String> getJvmArguments(LaunchOptions options) {
List<String> jvmArgs = new ArrayList<>(); List<String> jvmArgs = new ArrayList<>();
// Выделенная память int ramMB = options.getMaxMemory() > 0 ? options.getMaxMemory() : 4096;
int ramMB = options.getMaxMemory() > 0 ? options.getMaxMemory() : 2048;
jvmArgs.add("-Xmx" + ramMB + "M"); jvmArgs.add("-Xmx" + ramMB + "M");
jvmArgs.add("-Xms" + Math.max(512, ramMB / 2) + "M"); jvmArgs.add("-Xms" + Math.max(512, ramMB / 2) + "M");
// Стандартные оптимизации
jvmArgs.add("-XX:+UseG1GC"); jvmArgs.add("-XX:+UseG1GC");
jvmArgs.add("-XX:+UnlockExperimentalVMOptions"); jvmArgs.add("-XX:+UnlockExperimentalVMOptions");
jvmArgs.add("-XX:G1NewSizePercent=20"); jvmArgs.add("-XX:G1NewSizePercent=20");
@@ -72,12 +84,8 @@ public class LaunchCommandBuilder {
jvmArgs.add("-XX:MaxGCPauseMillis=50"); jvmArgs.add("-XX:MaxGCPauseMillis=50");
jvmArgs.add("-XX:G1HeapRegionSize=32M"); jvmArgs.add("-XX:G1HeapRegionSize=32M");
// Дополнительные JVM аргументы из настроек пользователя
if (options.getExtraJvmArgs() != null && !options.getExtraJvmArgs().isEmpty()) {
jvmArgs.addAll(options.getExtraJvmArgs());
}
String loaderType = instance.getLoaderType().toLowerCase(); String loaderType = instance.getLoaderType().toLowerCase();
if ("fabric".equals(loaderType)) { if ("fabric".equals(loaderType)) {
jvmArgs.add("--add-modules=ALL-MODULE-PATH"); jvmArgs.add("--add-modules=ALL-MODULE-PATH");
jvmArgs.add("--add-opens=java.base/java.io=ALL-UNNAMED"); jvmArgs.add("--add-opens=java.base/java.io=ALL-UNNAMED");
@@ -91,15 +99,43 @@ public class LaunchCommandBuilder {
jvmArgs.add("--add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED"); 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<String> getForgeJvmArguments() {
List<String> 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; return jvmArgs;
} }
private String buildClasspath() throws Exception { private String buildClasspath() throws Exception {
List<String> paths = new ArrayList<>(); List<String> paths = new ArrayList<>();
//String loaderType = instance.getLoaderType().toLowerCase(); String versionId = getVersionId(); // ← используем getVersionId()
String versionId = getVersionId();
// Добавляем основной jar
Path versionJar = instance.getPath() Path versionJar = instance.getPath()
.resolve("versions") .resolve("versions")
.resolve(versionId) .resolve(versionId)
@@ -108,18 +144,18 @@ public class LaunchCommandBuilder {
if (Files.exists(versionJar)) { if (Files.exists(versionJar)) {
paths.add(versionJar.toAbsolutePath().toString()); paths.add(versionJar.toAbsolutePath().toString());
} else { } else {
Path altVersionJar = instance.getPath() // Fallback на vanilla версию
String mcVersion = instance.getMinecraftVersion();
Path fallbackJar = instance.getPath()
.resolve("versions") .resolve("versions")
.resolve(instance.getMinecraftVersion()) .resolve(mcVersion)
.resolve(instance.getMinecraftVersion() + ".jar"); .resolve(mcVersion + ".jar");
if (Files.exists(fallbackJar)) {
if (Files.exists(altVersionJar)) { paths.add(fallbackJar.toAbsolutePath().toString());
paths.add(altVersionJar.toAbsolutePath().toString());
} else {
System.err.println(ZAnsi.yellow("Warning: Vanilla Minecraft jar not found at: " + versionJar));
} }
} }
// Все библиотеки
Path librariesDir = instance.getPath().resolve("libraries"); Path librariesDir = instance.getPath().resolve("libraries");
if (Files.exists(librariesDir)) { if (Files.exists(librariesDir)) {
try (var stream = Files.walk(librariesDir)) { try (var stream = Files.walk(librariesDir)) {
@@ -133,16 +169,92 @@ public class LaunchCommandBuilder {
return String.join(separator, paths); return String.join(separator, paths);
} }
private String buildForgeClasspath() throws Exception {
List<String> 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);
}
private String getMainClass() { private String getMainClass() {
String loaderType = instance.getLoaderType().toLowerCase(); String loaderType = instance.getLoaderType().toLowerCase();
if ("fabric".equals(loaderType)) { if ("fabric".equals(loaderType)) {
// Fabric 0.14+ использует KnotClient
return "net.fabricmc.loader.impl.launch.knot.KnotClient"; return "net.fabricmc.loader.impl.launch.knot.KnotClient";
} }
else if ("forge".equals(loaderType)) { else if ("forge".equals(loaderType)) {
// Forge 1.20.1 использует ClientModLoader return "cpw.mods.modlauncher.Launcher";
return "net.minecraftforge.client.loading.ClientModLoader";
} }
else { else {
return "net.minecraft.client.main.Main"; return "net.minecraft.client.main.Main";
@@ -168,15 +280,64 @@ public class LaunchCommandBuilder {
args.add(options.getUsername() != null ? options.getUsername() : "Player"); args.add(options.getUsername() != null ? options.getUsername() : "Player");
args.add("--accessToken"); args.add("--accessToken");
args.add("0"); // потом токен от блядкого сервера args.add(options.getAccessToken() != null ? options.getAccessToken() : "0");
args.add("--uuid"); 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()));
}
if (options.getHeight() > 0) {
args.add("--height");
args.add(String.valueOf(options.getHeight()));
}
return args;
}
private List<String> getForgeArguments(LaunchOptions options) {
List<String> 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("--userType");
args.add("legacy"); args.add("legacy");
// Дополнительные параметры
if (options.getWidth() > 0) { if (options.getWidth() > 0) {
args.add("--width"); args.add("--width");
args.add(String.valueOf(options.getWidth())); args.add(String.valueOf(options.getWidth()));
@@ -198,14 +359,14 @@ public class LaunchCommandBuilder {
return mcVersion; return mcVersion;
} }
else if ("fabric".equals(loaderType)) { else if ("fabric".equals(loaderType)) {
// Fabric использует vanilla версию для jar файла
return mcVersion; return mcVersion;
} }
else if ("forge".equals(loaderType)) { else if ("forge".equals(loaderType)) {
// Forge создаёт свою версию в папке versions
return mcVersion + "-forge-" + loaderVer; return mcVersion + "-forge-" + loaderVer;
} }
return mcVersion; return mcVersion;
} }
} }
@@ -52,6 +52,17 @@ public class ProgressBar {
System.out.flush(); 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) { public static void finish(String message) {
System.out.println("\r" + ZAnsi.brightGreen(message + " завершено ✓")); System.out.println("\r" + ZAnsi.brightGreen(message + " завершено ✓"));
System.out.flush(); System.out.flush();