чиним cli + ui | Cli 99% готовность, UI примерно 70%
This commit is contained in:
@@ -51,34 +51,34 @@ public class Bootstrap {
|
|||||||
isCliMode = argList.contains("--cli");
|
isCliMode = argList.contains("--cli");
|
||||||
isJfxMode = !isCliMode;
|
isJfxMode = !isCliMode;
|
||||||
|
|
||||||
log("Режим: " + (isCliMode ? "CLI" : "JFX"));
|
log("Mode: " + (isCliMode ? "CLI" : "JFX"));
|
||||||
|
|
||||||
String currentVersion = readCurrentVersion();
|
String currentVersion = readCurrentVersion();
|
||||||
String serverVersion = getServerVersion();
|
String serverVersion = getServerVersion();
|
||||||
|
|
||||||
log("Локальная версия: " + currentVersion);
|
log("Local version: " + currentVersion);
|
||||||
log("Версия на сервере: " + serverVersion);
|
log("Server version: " + serverVersion);
|
||||||
|
|
||||||
loadMirrors();
|
loadMirrors();
|
||||||
log("Основной сервер: " + BASE_URL);
|
log("Primary server: " + BASE_URL);
|
||||||
log("Mirrors доступны: " + (MIRRORS.size() + 1));
|
log("Mirrors available: " + (MIRRORS.size() + 1));
|
||||||
|
|
||||||
if (isNewer(serverVersion, currentVersion)) {
|
if (isNewer(serverVersion, currentVersion)) {
|
||||||
log("Доступно обновление!");
|
log("Update available!");
|
||||||
downloadUpdate(serverVersion);
|
downloadUpdate(serverVersion);
|
||||||
} else {
|
} else {
|
||||||
log("Версия актуальна");
|
log("Version is up to date");
|
||||||
}
|
}
|
||||||
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
log("Получен сигнал завершения...");
|
log("Shutdown signal received...");
|
||||||
}));
|
}));
|
||||||
|
|
||||||
launchMain(args);
|
launchMain(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void launchMain(String[] args) throws Exception {
|
private static void launchMain(String[] args) throws Exception {
|
||||||
log("Загрузка лаунчера: " + getLauncherJar());
|
log("Loading launcher: " + getLauncherJar());
|
||||||
|
|
||||||
if (isCliMode) {
|
if (isCliMode) {
|
||||||
launchInProcess(args);
|
launchInProcess(args);
|
||||||
@@ -105,9 +105,15 @@ public class Bootstrap {
|
|||||||
|
|
||||||
private static void launchInNewProcess(String[] args) throws Exception {
|
private static void launchInNewProcess(String[] args) throws Exception {
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
String javaExe = os.contains("windows") ? "java.exe" : "java";
|
|
||||||
|
|
||||||
Path javaBin = findJava(false);
|
Path javaBin = findJava(false);
|
||||||
|
// On Windows, use javaw.exe to hide console in JFX mode
|
||||||
|
if (os.contains("windows")) {
|
||||||
|
Path javawPath = javaBin.resolveSibling("javaw.exe");
|
||||||
|
if (Files.exists(javawPath)) {
|
||||||
|
javaBin = javawPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
Path javafxPath = baseDir.resolve("lib").resolve("javafx");
|
Path javafxPath = baseDir.resolve("lib").resolve("javafx");
|
||||||
|
|
||||||
List<String> cmd = new ArrayList<>();
|
List<String> cmd = new ArrayList<>();
|
||||||
@@ -132,12 +138,12 @@ public class Bootstrap {
|
|||||||
pb.directory(baseDir.toFile());
|
pb.directory(baseDir.toFile());
|
||||||
pb.inheritIO();
|
pb.inheritIO();
|
||||||
|
|
||||||
log("Запуск процесса: " + String.join(" ", cmd));
|
log("Starting process: " + String.join(" ", cmd));
|
||||||
|
|
||||||
Process p = pb.start();
|
Process p = pb.start();
|
||||||
int code = p.waitFor();
|
int code = p.waitFor();
|
||||||
|
|
||||||
log("JFX процесс завершился с кодом: " + code);
|
log("JFX process exited with code: " + code);
|
||||||
System.exit(code);
|
System.exit(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +168,7 @@ public class Bootstrap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!Files.exists(javaBin)) {
|
if (!Files.exists(javaBin)) {
|
||||||
throw new RuntimeException("Java не найдена");
|
throw new RuntimeException("Java not found");
|
||||||
}
|
}
|
||||||
return javaBin;
|
return javaBin;
|
||||||
}
|
}
|
||||||
@@ -186,7 +192,7 @@ public class Bootstrap {
|
|||||||
if (v != null && !v.isBlank()) return v;
|
if (v != null && !v.isBlank()) return v;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log("Ошибка чтения манифеста: " + e.getMessage());
|
log("Error reading manifest: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "0.0.0";
|
return "0.0.0";
|
||||||
@@ -219,7 +225,7 @@ public class Bootstrap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log("Ошибка получения версии: " + e.getMessage());
|
log("Error fetching version: " + e.getMessage());
|
||||||
}
|
}
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
@@ -240,21 +246,18 @@ public class Bootstrap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void downloadUpdate(String newVersion) throws Exception {
|
private static void downloadUpdate(String newVersion) throws Exception {
|
||||||
log("Проверка обновлений...");
|
log("Checking for updates...");
|
||||||
|
|
||||||
// Получаем мета с сервера
|
|
||||||
Map<String, FileMeta> serverFiles = fetchServerMeta(newVersion);
|
Map<String, FileMeta> serverFiles = fetchServerMeta(newVersion);
|
||||||
if (serverFiles.isEmpty()) {
|
if (serverFiles.isEmpty()) {
|
||||||
log("Не удалось получить мета с сервера");
|
log("Failed to get server meta");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сканируем локальные файлы
|
|
||||||
Map<String, String> localFiles = scanLocalFiles();
|
Map<String, String> localFiles = scanLocalFiles();
|
||||||
log("Локальных файлов: " + localFiles.size());
|
log("Local files: " + localFiles.size());
|
||||||
log("Файлов на сервере: " + serverFiles.size());
|
log("Server files: " + serverFiles.size());
|
||||||
|
|
||||||
// Сравниваем и скачиваем
|
|
||||||
int downloaded = 0;
|
int downloaded = 0;
|
||||||
int skipped = 0;
|
int skipped = 0;
|
||||||
|
|
||||||
@@ -271,17 +274,17 @@ public class Bootstrap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (localHash != null) {
|
if (localHash != null) {
|
||||||
log("Обновление: " + filePath);
|
log("Updating: " + filePath);
|
||||||
} else {
|
} else {
|
||||||
log("Скачивание: " + filePath);
|
log("Downloading: " + filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadFile(newVersion, filePath, serverMeta.size);
|
downloadFile(newVersion, filePath, serverMeta.size);
|
||||||
downloaded++;
|
downloaded++;
|
||||||
}
|
}
|
||||||
|
|
||||||
log("Обновлено файлов: " + downloaded + ", пропущено: " + skipped);
|
log("Updated files: " + downloaded + ", skipped: " + skipped);
|
||||||
log("Обновлено до v" + newVersion);
|
log("Updated to v" + newVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, FileMeta> fetchServerMeta(String version) {
|
private static Map<String, FileMeta> fetchServerMeta(String version) {
|
||||||
@@ -311,7 +314,7 @@ public class Bootstrap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log("Ошибка получения мета: " + e.getMessage());
|
log("Error fetching meta: " + e.getMessage());
|
||||||
}
|
}
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
@@ -349,7 +352,6 @@ public class Bootstrap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void downloadFile(String version, String filePath, long expectedSize) throws Exception {
|
private static void downloadFile(String version, String filePath, long expectedSize) throws Exception {
|
||||||
// Пробуем все сервера
|
|
||||||
List<String> servers = new ArrayList<>();
|
List<String> servers = new ArrayList<>();
|
||||||
if (isServerReachable(BASE_URL)) servers.add(BASE_URL);
|
if (isServerReachable(BASE_URL)) servers.add(BASE_URL);
|
||||||
servers.addAll(MIRRORS);
|
servers.addAll(MIRRORS);
|
||||||
@@ -365,7 +367,6 @@ public class Bootstrap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Последний резерв - основной сервер
|
|
||||||
downloadFileFromServer(BASE_URL + "/launcher/file/" + version + "/" + filePath, expectedSize, filePath);
|
downloadFileFromServer(BASE_URL + "/launcher/file/" + version + "/" + filePath, expectedSize, filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +399,6 @@ public class Bootstrap {
|
|||||||
out.write(buf, 0, len);
|
out.write(buf, 0, len);
|
||||||
downloaded += len;
|
downloaded += len;
|
||||||
|
|
||||||
// Обновляем прогресс на каждом KB
|
|
||||||
if (downloaded - lastUpdate > 1024 || downloaded == expectedSize) {
|
if (downloaded - lastUpdate > 1024 || downloaded == expectedSize) {
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
double speed = downloaded / 1024.0 / 1024.0 / (elapsed / 1000.0 + 0.001);
|
double speed = downloaded / 1024.0 / 1024.0 / (elapsed / 1000.0 + 0.001);
|
||||||
@@ -417,10 +417,9 @@ public class Bootstrap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Финальная строка
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
double speed = downloaded / 1024.0 / 1024.0 / (elapsed / 1000.0 + 0.001);
|
double speed = downloaded / 1024.0 / 1024.0 / (elapsed / 1000.0 + 0.001);
|
||||||
System.out.println(String.format("\r[%s] %s - %.1f MB (%.1f MB/s) - Готово!",
|
System.out.println(String.format("\r[%s] %s - %.1f MB (%.1f MB/s) - Done!",
|
||||||
getProgressBar(downloaded, expectedSize),
|
getProgressBar(downloaded, expectedSize),
|
||||||
fileName,
|
fileName,
|
||||||
downloaded / 1024.0 / 1024.0,
|
downloaded / 1024.0 / 1024.0,
|
||||||
@@ -474,7 +473,7 @@ public class Bootstrap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log("Mirrors недоступны: " + e.getMessage());
|
log("Mirrors unavailable: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -25,26 +26,23 @@ public class Bootstrap {
|
|||||||
|
|
||||||
log("=== ZernMC Launcher ===");
|
log("=== ZernMC Launcher ===");
|
||||||
|
|
||||||
// Определяем режим запуска
|
|
||||||
List<String> argList = Arrays.asList(args);
|
List<String> argList = Arrays.asList(args);
|
||||||
boolean cliMode = argList.contains("--cli");
|
boolean cliMode = argList.contains("--cli");
|
||||||
boolean jfxMode = !cliMode; // по умолчанию JFX
|
boolean jfxMode = !cliMode;
|
||||||
|
|
||||||
// Проверка и обновление лаунчера
|
|
||||||
String currentVersion = readCurrentVersion();
|
String currentVersion = readCurrentVersion();
|
||||||
String serverVersion = getServerVersion();
|
String serverVersion = getServerVersion();
|
||||||
|
|
||||||
log("Локальная версия: " + currentVersion);
|
log("Local version: " + currentVersion);
|
||||||
log("Версия на сервере: " + serverVersion);
|
log("Server version: " + serverVersion);
|
||||||
|
|
||||||
if (isNewer(serverVersion, currentVersion)) {
|
if (isNewer(serverVersion, currentVersion)) {
|
||||||
log("Доступно обновление!");
|
log("Update available!");
|
||||||
downloadUpdate(serverVersion);
|
downloadUpdate(serverVersion);
|
||||||
} else {
|
} else {
|
||||||
log("Версия актуальна");
|
log("Version is up to date");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Запуск в выбранном режиме
|
|
||||||
if (jfxMode) {
|
if (jfxMode) {
|
||||||
launchJFX();
|
launchJFX();
|
||||||
} else {
|
} else {
|
||||||
@@ -118,10 +116,10 @@ public class Bootstrap {
|
|||||||
while ((len = in.read(buf)) > 0) {
|
while ((len = in.read(buf)) > 0) {
|
||||||
out.write(buf, 0, len);
|
out.write(buf, 0, len);
|
||||||
total += len;
|
total += len;
|
||||||
System.out.print("\rСкачано: " + (total/1024/1024) + " MB");
|
System.out.print("\rDownloaded: " + (total/1024/1024) + " MB");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log("Скачано");
|
log("Downloaded");
|
||||||
|
|
||||||
Path backup = jarFile.resolveSibling(JAR_NAME + ".old");
|
Path backup = jarFile.resolveSibling(JAR_NAME + ".old");
|
||||||
|
|
||||||
@@ -130,9 +128,9 @@ public class Bootstrap {
|
|||||||
if (Files.exists(backup)) Files.delete(backup);
|
if (Files.exists(backup)) Files.delete(backup);
|
||||||
|
|
||||||
Files.writeString(baseDir.resolve(VERSION_FILE), newVersion);
|
Files.writeString(baseDir.resolve(VERSION_FILE), newVersion);
|
||||||
log("Обновлено до v" + newVersion);
|
log("Updated to v" + newVersion);
|
||||||
} else {
|
} else {
|
||||||
throw new IOException("Сервер вернул код: " + conn.getResponseCode());
|
throw new IOException("Server returned code: " + conn.getResponseCode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +138,7 @@ public class Bootstrap {
|
|||||||
Path javaBin = findJava();
|
Path javaBin = findJava();
|
||||||
Path jarPath = baseDir.resolve(JAR_NAME);
|
Path jarPath = baseDir.resolve(JAR_NAME);
|
||||||
|
|
||||||
log("Запуск JFX режима...");
|
log("Starting JFX mode...");
|
||||||
log("Java: " + javaBin);
|
log("Java: " + javaBin);
|
||||||
log("JAR: " + jarPath);
|
log("JAR: " + jarPath);
|
||||||
|
|
||||||
@@ -176,7 +174,7 @@ public class Bootstrap {
|
|||||||
|
|
||||||
int code = p.waitFor();
|
int code = p.waitFor();
|
||||||
try { outputThread.interrupt(); } catch (Exception ignored) {}
|
try { outputThread.interrupt(); } catch (Exception ignored) {}
|
||||||
log("Завершено с кодом: " + code);
|
log("Exited with code: " + code);
|
||||||
System.exit(code);
|
System.exit(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +182,7 @@ public class Bootstrap {
|
|||||||
Path javaBin = findJava();
|
Path javaBin = findJava();
|
||||||
Path jarPath = baseDir.resolve(JAR_NAME);
|
Path jarPath = baseDir.resolve(JAR_NAME);
|
||||||
|
|
||||||
log("Запуск CLI режима...");
|
log("Starting CLI mode...");
|
||||||
log("Java: " + javaBin);
|
log("Java: " + javaBin);
|
||||||
log("JAR: " + jarPath);
|
log("JAR: " + jarPath);
|
||||||
|
|
||||||
@@ -220,7 +218,7 @@ public class Bootstrap {
|
|||||||
|
|
||||||
int code = p.waitFor();
|
int code = p.waitFor();
|
||||||
try { outputThread.interrupt(); } catch (Exception ignored) {}
|
try { outputThread.interrupt(); } catch (Exception ignored) {}
|
||||||
log("Завершено с кодом: " + code);
|
log("Exited with code: " + code);
|
||||||
System.exit(code);
|
System.exit(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,15 +226,12 @@ public class Bootstrap {
|
|||||||
String os = System.getProperty("os.name").toLowerCase();
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
String javaExe = os.contains("windows") ? "java.exe" : "java";
|
String javaExe = os.contains("windows") ? "java.exe" : "java";
|
||||||
|
|
||||||
// Сначала ищем jre21/bin/java рядом с лаунчером
|
|
||||||
Path javaBin = baseDir.resolve("jre21").resolve("bin").resolve(javaExe);
|
Path javaBin = baseDir.resolve("jre21").resolve("bin").resolve(javaExe);
|
||||||
|
|
||||||
// Если нет, пробуем системную Java
|
|
||||||
if (!Files.exists(javaBin)) {
|
if (!Files.exists(javaBin)) {
|
||||||
javaBin = Paths.get(System.getProperty("java.home"), "bin", javaExe);
|
javaBin = Paths.get(System.getProperty("java.home"), "bin", javaExe);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если и это не найдено - ищем java в PATH
|
|
||||||
if (!Files.exists(javaBin)) {
|
if (!Files.exists(javaBin)) {
|
||||||
try {
|
try {
|
||||||
Process p = new ProcessBuilder("which", javaExe).start();
|
Process p = new ProcessBuilder("which", javaExe).start();
|
||||||
@@ -252,7 +247,7 @@ public class Bootstrap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!Files.exists(javaBin)) {
|
if (!Files.exists(javaBin)) {
|
||||||
throw new RuntimeException("Java не найдена. Убедитесь, что jre21 присутствует в папке с лаунчером или Java установлена в системе");
|
throw new RuntimeException("Java not found. Make sure jre21 is present in the launcher folder or Java is installed on the system");
|
||||||
}
|
}
|
||||||
|
|
||||||
return javaBin;
|
return javaBin;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class Main {
|
|||||||
|
|
||||||
ZAnsi.install();
|
ZAnsi.install();
|
||||||
System.out.print("\033[H\033[2J");
|
System.out.print("\033[H\033[2J");
|
||||||
System.out.println(ZAnsi.brightGreen("Добро пожаловать в ZernMC Launcher " + CURRENT_VERSION));
|
System.out.println(ZAnsi.brightGreen("Welcome to ZernMC Launcher " + CURRENT_VERSION));
|
||||||
|
|
||||||
List<String> argList = List.of(args);
|
List<String> argList = List.of(args);
|
||||||
boolean jfxMode = argList.contains("--jfx");
|
boolean jfxMode = argList.contains("--jfx");
|
||||||
@@ -41,22 +41,22 @@ public class Main {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
System.out.print("\033[H\033[2J");
|
||||||
|
System.out.println(ZAnsi.brightGreen("Welcome to ZernMC Launcher " + CURRENT_VERSION));
|
||||||
|
|
||||||
startCLI();
|
startCLI();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void launchJFX() {
|
private static void launchJFX() {
|
||||||
System.out.println(ZAnsi.cyan("Запуск JFX интерфейса..."));
|
|
||||||
try {
|
try {
|
||||||
// Устанавливаем параметры для JavaFX (важно для Windows)
|
|
||||||
System.setProperty("javafx.runtime.version", "21");
|
System.setProperty("javafx.runtime.version", "21");
|
||||||
|
|
||||||
JFXLauncher.main(new String[]{});
|
JFXLauncher.main(new String[]{});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println(ZAnsi.brightRed("Ошибка запуска JFX: " + e.getMessage()));
|
System.err.println(ZAnsi.brightRed("Error starting JFX: " + e.getMessage()));
|
||||||
// Проверяем, связано ли это с отсутствием JavaFX
|
|
||||||
if (e.getMessage() != null && e.getMessage().contains("QuantumRenderer")) {
|
if (e.getMessage() != null && e.getMessage().contains("QuantumRenderer")) {
|
||||||
System.err.println(ZAnsi.yellow("JavaFX недоступен. Возможно, отсутствуют нативные библиотеки."));
|
System.err.println(ZAnsi.yellow("JavaFX is not available. Native libraries may be missing."));
|
||||||
System.err.println(ZAnsi.yellow("Попробуйте использовать CLI режим: --cli"));
|
System.err.println(ZAnsi.yellow("Try CLI mode: --cli"));
|
||||||
}
|
}
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
@@ -66,36 +66,34 @@ public class Main {
|
|||||||
private static void startCLI() throws IOException {
|
private static void startCLI() throws IOException {
|
||||||
ZHttpClient.checkAllServicesOnStartup(true);
|
ZHttpClient.checkAllServicesOnStartup(true);
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("Проверка авторизации..."));
|
System.out.println(ZAnsi.cyan("Checking authorization..."));
|
||||||
var sessionResponse = api.checkSession();
|
var sessionResponse = api.checkSession();
|
||||||
|
|
||||||
if (!sessionResponse.isSuccess()) {
|
if (!sessionResponse.isSuccess()) {
|
||||||
LoginMenu loginMenu = new LoginMenu();
|
LoginMenu loginMenu = new LoginMenu();
|
||||||
boolean loggedIn = loginMenu.show();
|
boolean loggedIn = loginMenu.show();
|
||||||
if (!loggedIn) {
|
if (!loggedIn) {
|
||||||
System.out.println(ZAnsi.yellow("До свидания!"));
|
System.out.println(ZAnsi.yellow("Goodbye!"));
|
||||||
ZAnsi.uninstall();
|
ZAnsi.uninstall();
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var sessionInfo = sessionResponse.getData();
|
var sessionInfo = sessionResponse.getData();
|
||||||
System.out.println(ZAnsi.brightGreen("Добро пожаловать обратно, " + sessionInfo.getUsername() + "!"));
|
System.out.println(ZAnsi.brightGreen("Welcome back, " + sessionInfo.getUsername() + "!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("Запуск CLI режима..."));
|
System.out.println(ZAnsi.cyan("Starting CLI mode..."));
|
||||||
|
|
||||||
// === ГЛАВНЫЙ ЦИКЛ ===
|
|
||||||
try {
|
try {
|
||||||
mainLoop();
|
mainLoop();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println(ZAnsi.brightRed("Критическая ошибка: " + e.getMessage()));
|
System.err.println(ZAnsi.brightRed("Critical error: " + e.getMessage()));
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
ZAnsi.uninstall();
|
ZAnsi.uninstall();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== ГЛАВНЫЙ ЦИКЛ ======================
|
|
||||||
private static void mainLoop() throws Exception {
|
private static void mainLoop() throws Exception {
|
||||||
if (Config.isZernMCBuild()) {
|
if (Config.isZernMCBuild()) {
|
||||||
zernMCFlow();
|
zernMCFlow();
|
||||||
@@ -104,24 +102,21 @@ public class Main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== ZERNMC FLOW ======================
|
|
||||||
private static void zernMCFlow() throws Exception {
|
private static void zernMCFlow() throws Exception {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.header("=== ZernMC Private Launcher ==="));
|
System.out.println(ZAnsi.header("=== ZernMC Private Launcher ==="));
|
||||||
|
|
||||||
// 1. Проверка подключения к серверу
|
System.out.println(ZAnsi.cyan("Checking connection to ZernMC server..."));
|
||||||
System.out.println(ZAnsi.cyan("Проверка подключения к ZernMC серверу..."));
|
|
||||||
try {
|
try {
|
||||||
String response = ZHttpClient.get("/health");
|
String response = ZHttpClient.get("/health");
|
||||||
System.out.println(ZAnsi.brightGreen("✓ Сервер доступен"));
|
System.out.println(ZAnsi.brightGreen("✓ Server is available"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.brightRed("✗ Не удалось подключиться к ZernMC серверу"));
|
System.out.println(ZAnsi.brightRed("✗ Could not connect to ZernMC server"));
|
||||||
System.out.println(ZAnsi.white("Ошибка: " + e.getMessage()));
|
System.out.println(ZAnsi.white("Error: " + e.getMessage()));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Авторизация
|
|
||||||
boolean sessionRestored = AuthManager.loadSavedSession();
|
boolean sessionRestored = AuthManager.loadSavedSession();
|
||||||
if (!sessionRestored) {
|
if (!sessionRestored) {
|
||||||
LoginMenu loginMenu = new LoginMenu();
|
LoginMenu loginMenu = new LoginMenu();
|
||||||
@@ -130,38 +125,36 @@ public class Main {
|
|||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightGreen("Добро пожаловать обратно, " + AuthManager.getUsername() + "!"));
|
System.out.println(ZAnsi.brightGreen("Welcome back, " + AuthManager.getUsername() + "!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Запуск меню (LaunchMenu сам определит режим и вызовет нужный flow)
|
|
||||||
LaunchMenu launchMenu = new LaunchMenu();
|
LaunchMenu launchMenu = new LaunchMenu();
|
||||||
launchMenu.show(); // ← Здесь будет вызван showZernMCOnly() внутри
|
launchMenu.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== GLOBAL FLOW ======================
|
|
||||||
private static void globalFlow() throws Exception {
|
private static void globalFlow() throws Exception {
|
||||||
while (true) {
|
while (true) {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.header("=== ZernMC Launcher ==="));
|
System.out.println(ZAnsi.header("=== ZernMC Launcher ==="));
|
||||||
|
|
||||||
List<String> options = List.of(
|
List<String> options = List.of(
|
||||||
"Запустить игру",
|
"Launch Game",
|
||||||
"Проверка обновлений",
|
"Check Updates",
|
||||||
"Настройки",
|
"Settings",
|
||||||
"Проверка подключения к серверам",
|
"Server Connection Check",
|
||||||
"Выход"
|
"Exit"
|
||||||
);
|
);
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu("Главное меню", options);
|
ArrowMenu menu = new ArrowMenu("Main Menu", options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
|
|
||||||
if (choice == -1 || choice == 4) {
|
if (choice == -1 || choice == 4) {
|
||||||
System.out.println(ZAnsi.yellow("До свидания!"));
|
System.out.println(ZAnsi.yellow("Goodbye!"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (choice) {
|
switch (choice) {
|
||||||
case 0 -> new LaunchMenu().show(); // обычный LaunchMenu
|
case 0 -> new LaunchMenu().show();
|
||||||
case 1 -> new UpdateMenu().show();
|
case 1 -> new UpdateMenu().show();
|
||||||
case 2 -> new SettingsMenu().show();
|
case 2 -> new SettingsMenu().show();
|
||||||
case 3 -> new ServerCheckMenu().show();
|
case 3 -> new ServerCheckMenu().show();
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ public class LauncherAPI {
|
|||||||
return launchService;
|
return launchService;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== Удобные методы ======================
|
|
||||||
|
|
||||||
public boolean isLoggedIn() {
|
public boolean isLoggedIn() {
|
||||||
return authService.isLoggedIn();
|
return authService.isLoggedIn();
|
||||||
}
|
}
|
||||||
@@ -55,6 +53,14 @@ public class LauncherAPI {
|
|||||||
return authService.logout();
|
return authService.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ApiResponse<Boolean> activatePass(String passCode) {
|
||||||
|
return authService.activatePass(passCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiResponse<AuthService.LoginResult> register(String username, String password) {
|
||||||
|
return authService.register(username, password);
|
||||||
|
}
|
||||||
|
|
||||||
public ApiResponse<List<InstanceService.InstanceInfo>> getAllInstances() {
|
public ApiResponse<List<InstanceService.InstanceInfo>> getAllInstances() {
|
||||||
return instanceService.getAllInstances();
|
return instanceService.getAllInstances();
|
||||||
}
|
}
|
||||||
@@ -83,7 +89,7 @@ public class LauncherAPI {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("[API] MC versions fetch failed: " + e.getMessage());
|
System.out.println("[API] MC versions fetch failed: " + e.getMessage());
|
||||||
}
|
}
|
||||||
return ApiResponse.error("Не удалось загрузить версии Minecraft");
|
return ApiResponse.error("Failed to load Minecraft versions");
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApiResponse<List<String>> getLoaderVersions(String mcVersion, String loader) {
|
public ApiResponse<List<String>> getLoaderVersions(String mcVersion, String loader) {
|
||||||
@@ -130,7 +136,7 @@ public class LauncherAPI {
|
|||||||
return ApiResponse.success(versions);
|
return ApiResponse.success(versions);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("[API] Loader versions fetch failed: " + e.getMessage());
|
System.out.println("[API] Loader versions fetch failed: " + e.getMessage());
|
||||||
return ApiResponse.error("Не удалось загрузить версии лоадера");
|
return ApiResponse.error("Failed to load loader versions");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +153,7 @@ public class LauncherAPI {
|
|||||||
try {
|
try {
|
||||||
String token = authService.getCurrentToken();
|
String token = authService.getCurrentToken();
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
return ApiResponse.error("Не авторизован");
|
return ApiResponse.error("Not logged in");
|
||||||
}
|
}
|
||||||
|
|
||||||
String response = ZHttpClient.get("/packs");
|
String response = ZHttpClient.get("/packs");
|
||||||
@@ -167,7 +173,7 @@ public class LauncherAPI {
|
|||||||
return ApiResponse.success(packs);
|
return ApiResponse.success(packs);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("[API] Packs fetch failed: " + e.getMessage());
|
System.out.println("[API] Packs fetch failed: " + e.getMessage());
|
||||||
return ApiResponse.error("Ошибка загрузки сборок: " + e.getMessage());
|
return ApiResponse.error("Failed to load packs: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-6
@@ -8,6 +8,27 @@ import java.io.IOException;
|
|||||||
|
|
||||||
public class AuthService {
|
public class AuthService {
|
||||||
|
|
||||||
|
public ApiResponse<LoginResult> register(String username, String password) {
|
||||||
|
try {
|
||||||
|
String response = post("/auth/register",
|
||||||
|
"{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}");
|
||||||
|
|
||||||
|
// If registration succeeds, auto-login
|
||||||
|
AuthManager.AuthResult result = AuthManager.login(username, password);
|
||||||
|
if (result.success) {
|
||||||
|
LoginResult loginResult = new LoginResult(AuthManager.getUsername(), AuthManager.getAccessToken());
|
||||||
|
return ApiResponse.success(loginResult);
|
||||||
|
}
|
||||||
|
return ApiResponse.error(result.error != null ? result.error : "Registration failed");
|
||||||
|
} catch (Exception e) {
|
||||||
|
String msg = e.getMessage();
|
||||||
|
if (msg != null && msg.contains("HTTP 409")) {
|
||||||
|
return ApiResponse.error("Username already taken");
|
||||||
|
}
|
||||||
|
return ApiResponse.error("Registration error: " + msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ApiResponse<LoginResult> login(String username, String password) {
|
public ApiResponse<LoginResult> login(String username, String password) {
|
||||||
try {
|
try {
|
||||||
AuthManager.AuthResult result = AuthManager.login(username, password);
|
AuthManager.AuthResult result = AuthManager.login(username, password);
|
||||||
@@ -15,9 +36,9 @@ public class AuthService {
|
|||||||
LoginResult loginResult = new LoginResult(AuthManager.getUsername(), AuthManager.getAccessToken());
|
LoginResult loginResult = new LoginResult(AuthManager.getUsername(), AuthManager.getAccessToken());
|
||||||
return ApiResponse.success(loginResult);
|
return ApiResponse.success(loginResult);
|
||||||
}
|
}
|
||||||
return ApiResponse.error(result.error != null ? result.error : "Неверный логин или пароль");
|
return ApiResponse.error(result.error != null ? result.error : "Invalid login or password");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ApiResponse.error("Ошибка авторизации: " + e.getMessage());
|
return ApiResponse.error("Auth error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +47,7 @@ public class AuthService {
|
|||||||
AuthManager.logout();
|
AuthManager.logout();
|
||||||
return ApiResponse.success(true);
|
return ApiResponse.success(true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ApiResponse.error("Ошибка при выходе: " + e.getMessage());
|
return ApiResponse.error("Logout error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,9 +64,9 @@ public class AuthService {
|
|||||||
);
|
);
|
||||||
return ApiResponse.success(info);
|
return ApiResponse.success(info);
|
||||||
}
|
}
|
||||||
return ApiResponse.error("Сессия не найдена");
|
return ApiResponse.error("Session not found");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ApiResponse.error("Ошибка проверки сессии: " + e.getMessage());
|
return ApiResponse.error("Session check error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +76,7 @@ public class AuthService {
|
|||||||
"{\"code\":\"" + passCode + "\"}");
|
"{\"code\":\"" + passCode + "\"}");
|
||||||
return ApiResponse.success(true);
|
return ApiResponse.success(true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ApiResponse.error("Ошибка активации проходки: " + e.getMessage());
|
return ApiResponse.error("Pass activation error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+29
-13
@@ -18,7 +18,7 @@ public class InstanceService {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return ApiResponse.success(infoList);
|
return ApiResponse.success(infoList);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return ApiResponse.error("Ошибка получения списка сборок: " + e.getMessage());
|
return ApiResponse.error("Error getting instances list: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,11 +26,11 @@ public class InstanceService {
|
|||||||
try {
|
try {
|
||||||
Instance instance = InstanceManager.getInstance(name);
|
Instance instance = InstanceManager.getInstance(name);
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
return ApiResponse.error("Сборка не найдена: " + name);
|
return ApiResponse.error("Pack not found: " + name);
|
||||||
}
|
}
|
||||||
return ApiResponse.success(toInstanceInfo(instance));
|
return ApiResponse.success(toInstanceInfo(instance));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ApiResponse.error("Ошибка получения сборки: " + e.getMessage());
|
return ApiResponse.error("Error getting pack: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,12 +38,12 @@ public class InstanceService {
|
|||||||
try {
|
try {
|
||||||
boolean created = InstanceManager.createInstanceFolder(name);
|
boolean created = InstanceManager.createInstanceFolder(name);
|
||||||
if (!created) {
|
if (!created) {
|
||||||
return ApiResponse.error("Сборка с таким именем уже существует: " + name);
|
return ApiResponse.error("A pack with this name already exists: " + name);
|
||||||
}
|
}
|
||||||
Instance instance = InstanceManager.getInstance(name);
|
Instance instance = InstanceManager.getInstance(name);
|
||||||
return ApiResponse.success(toInstanceInfo(instance));
|
return ApiResponse.success(toInstanceInfo(instance));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return ApiResponse.error("Ошибка создания сборки: " + e.getMessage());
|
return ApiResponse.error("Error creating pack: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,11 +51,11 @@ public class InstanceService {
|
|||||||
try {
|
try {
|
||||||
boolean deleted = InstanceManager.deleteInstance(name);
|
boolean deleted = InstanceManager.deleteInstance(name);
|
||||||
if (!deleted) {
|
if (!deleted) {
|
||||||
return ApiResponse.error("Не удалось удалить сборку: " + name);
|
return ApiResponse.error("Failed to delete pack: " + name);
|
||||||
}
|
}
|
||||||
return ApiResponse.success(true);
|
return ApiResponse.success(true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ApiResponse.error("Ошибка удаления сборки: " + e.getMessage());
|
return ApiResponse.error("Error deleting pack: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,21 +64,24 @@ public class InstanceService {
|
|||||||
Instance instance = InstanceManager.getInstance(name);
|
Instance instance = InstanceManager.getInstance(name);
|
||||||
return ApiResponse.success(instance != null);
|
return ApiResponse.success(instance != null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ApiResponse.error("Ошибка проверки сборки: " + e.getMessage());
|
return ApiResponse.error("Error checking pack: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private InstanceInfo toInstanceInfo(Instance instance) {
|
private InstanceInfo toInstanceInfo(Instance instance) {
|
||||||
// Определяем категорию: ZernMC или локальная
|
|
||||||
String name = instance.getName().toLowerCase();
|
String name = instance.getName().toLowerCase();
|
||||||
String category = name.contains("zernmc") ? "zernmc" : "local";
|
String category = instance.isServerPack() ? "zernmc" : "local";
|
||||||
|
|
||||||
return new InstanceInfo(
|
return new InstanceInfo(
|
||||||
instance.getName(),
|
instance.getName(),
|
||||||
instance.getPath().toString(),
|
instance.getPath().toString(),
|
||||||
instance.getMinecraftVersion(),
|
instance.getMinecraftVersion(),
|
||||||
instance.getLoaderType(),
|
instance.getLoaderType(),
|
||||||
category
|
category,
|
||||||
|
instance.isServerPack(),
|
||||||
|
instance.getServerVersion(),
|
||||||
|
instance.getLoaderVersion(),
|
||||||
|
instance.getServerPackName()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,14 +90,23 @@ public class InstanceService {
|
|||||||
private String path;
|
private String path;
|
||||||
private String version;
|
private String version;
|
||||||
private String loaderType;
|
private String loaderType;
|
||||||
private String category; // "zernmc" или "local"
|
private String category;
|
||||||
|
private boolean isServerPack;
|
||||||
|
private int serverVersion;
|
||||||
|
private String loaderVersion;
|
||||||
|
private String serverPackName;
|
||||||
|
|
||||||
public InstanceInfo(String name, String path, String version, String loaderType, String category) {
|
public InstanceInfo(String name, String path, String version, String loaderType, String category,
|
||||||
|
boolean isServerPack, int serverVersion, String loaderVersion, String serverPackName) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.loaderType = loaderType;
|
this.loaderType = loaderType;
|
||||||
this.category = category;
|
this.category = category;
|
||||||
|
this.isServerPack = isServerPack;
|
||||||
|
this.serverVersion = serverVersion;
|
||||||
|
this.loaderVersion = loaderVersion;
|
||||||
|
this.serverPackName = serverPackName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() { return name; }
|
public String getName() { return name; }
|
||||||
@@ -102,5 +114,9 @@ public class InstanceService {
|
|||||||
public String getVersion() { return version; }
|
public String getVersion() { return version; }
|
||||||
public String getLoaderType() { return loaderType; }
|
public String getLoaderType() { return loaderType; }
|
||||||
public String getCategory() { return category; }
|
public String getCategory() { return category; }
|
||||||
|
public boolean isServerPack() { return isServerPack; }
|
||||||
|
public int getServerVersion() { return serverVersion; }
|
||||||
|
public String getLoaderVersion() { return loaderVersion; }
|
||||||
|
public String getServerPackName() { return serverPackName; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-13
@@ -7,6 +7,7 @@ import me.sashegdev.zernmc.launcher.minecraft.InstanceManager;
|
|||||||
import me.sashegdev.zernmc.launcher.minecraft.launch.LaunchCommandBuilder;
|
import me.sashegdev.zernmc.launcher.minecraft.launch.LaunchCommandBuilder;
|
||||||
import me.sashegdev.zernmc.launcher.minecraft.model.LaunchOptions;
|
import me.sashegdev.zernmc.launcher.minecraft.model.LaunchOptions;
|
||||||
import me.sashegdev.zernmc.launcher.ui.jfx.JFXLauncher;
|
import me.sashegdev.zernmc.launcher.ui.jfx.JFXLauncher;
|
||||||
|
import me.sashegdev.zernmc.launcher.utils.Config;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -35,11 +36,11 @@ public class LaunchService {
|
|||||||
try {
|
try {
|
||||||
Instance instance = InstanceManager.getInstance(instanceName);
|
Instance instance = InstanceManager.getInstance(instanceName);
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
return ApiResponse.error("Сборка не найдена: " + instanceName);
|
return ApiResponse.error("Pack not found: " + instanceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchCommandBuilder builder = new LaunchCommandBuilder(instance);
|
LaunchCommandBuilder builder = new LaunchCommandBuilder(instance);
|
||||||
LaunchOptions options = new LaunchOptions();
|
LaunchOptions options = createOptions();
|
||||||
|
|
||||||
List<String> command = builder.build(options);
|
List<String> command = builder.build(options);
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ public class LaunchService {
|
|||||||
);
|
);
|
||||||
return ApiResponse.success(info);
|
return ApiResponse.success(info);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ApiResponse.error("Ошибка подготовки запуска: " + e.getMessage());
|
return ApiResponse.error("Error preparing launch: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,12 +59,11 @@ public class LaunchService {
|
|||||||
try {
|
try {
|
||||||
Instance instance = InstanceManager.getInstance(instanceName);
|
Instance instance = InstanceManager.getInstance(instanceName);
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
return ApiResponse.error("Сборка не найдена: " + instanceName);
|
return ApiResponse.error("Pack not found: " + instanceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchCommandBuilder builder = new LaunchCommandBuilder(instance);
|
LaunchCommandBuilder builder = new LaunchCommandBuilder(instance);
|
||||||
LaunchOptions options = new LaunchOptions();
|
LaunchOptions options = createOptions();
|
||||||
|
|
||||||
options.setUsername(AuthManager.getUsername());
|
options.setUsername(AuthManager.getUsername());
|
||||||
options.setAccessToken(AuthManager.getAccessToken());
|
options.setAccessToken(AuthManager.getAccessToken());
|
||||||
options.setUuid(AuthManager.getUuid());
|
options.setUuid(AuthManager.getUuid());
|
||||||
@@ -100,7 +100,7 @@ public class LaunchService {
|
|||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
JFXLauncher.appendGameLog("[Ошибка чтения логов: " + e.getMessage() + "]");
|
JFXLauncher.appendGameLog("[Error reading logs: " + e.getMessage() + "]");
|
||||||
} finally {
|
} finally {
|
||||||
try { logFileOut.close(); } catch (Exception ignored) {}
|
try { logFileOut.close(); } catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
@@ -110,13 +110,13 @@ public class LaunchService {
|
|||||||
|
|
||||||
process.onExit().thenRun(() -> {
|
process.onExit().thenRun(() -> {
|
||||||
runningProcesses.remove(pid);
|
runningProcesses.remove(pid);
|
||||||
JFXLauncher.appendGameLog("[Minecraft завершился с кодом: " + process.exitValue() + "]");
|
JFXLauncher.appendGameLog("[Minecraft exited with code: " + process.exitValue() + "]");
|
||||||
});
|
});
|
||||||
|
|
||||||
ProcessInfo info = new ProcessInfo(instanceName, pid, "RUNNING");
|
ProcessInfo info = new ProcessInfo(instanceName, pid, "RUNNING");
|
||||||
return ApiResponse.success(info);
|
return ApiResponse.success(info);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ApiResponse.error("Ошибка запуска: " + e.getMessage());
|
return ApiResponse.error("Launch error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ public class LaunchService {
|
|||||||
try {
|
try {
|
||||||
Instance instance = InstanceManager.getInstance(instanceName);
|
Instance instance = InstanceManager.getInstance(instanceName);
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
return ApiResponse.error("Сборка не найдена: " + instanceName);
|
return ApiResponse.error("Pack not found: " + instanceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Path versionJson = instance.getPath().resolve("version.json");
|
Path versionJson = instance.getPath().resolve("version.json");
|
||||||
@@ -141,7 +141,7 @@ public class LaunchService {
|
|||||||
|
|
||||||
return ApiResponse.success(hasVersionJson);
|
return ApiResponse.success(hasVersionJson);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ApiResponse.error("Ошибка проверки готовности: " + e.getMessage());
|
return ApiResponse.error("Readiness check error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ public class LaunchService {
|
|||||||
try {
|
try {
|
||||||
Instance instance = InstanceManager.getInstance(instanceName);
|
Instance instance = InstanceManager.getInstance(instanceName);
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
return ApiResponse.error("Сборка не найдена: " + instanceName);
|
return ApiResponse.error("Pack not found: " + instanceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
InstanceInfo info = new InstanceInfo(
|
InstanceInfo info = new InstanceInfo(
|
||||||
@@ -161,10 +161,28 @@ public class LaunchService {
|
|||||||
);
|
);
|
||||||
return ApiResponse.success(info);
|
return ApiResponse.success(info);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ApiResponse.error("Ошибка получения информации: " + e.getMessage());
|
return ApiResponse.error("Info retrieval error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static LaunchOptions createOptions() {
|
||||||
|
LaunchOptions options = new LaunchOptions();
|
||||||
|
options.setMaxMemory(Config.getMaxMemory());
|
||||||
|
options.setWidth(Config.getWindowWidth());
|
||||||
|
options.setHeight(Config.getWindowHeight());
|
||||||
|
options.setJavaPath(Config.getJavaPath());
|
||||||
|
String args = Config.getExtraJvmArgs();
|
||||||
|
if (args != null && !args.isEmpty()) {
|
||||||
|
List<String> extraArgs = new ArrayList<>();
|
||||||
|
for (String arg : args.split("\\s+")) {
|
||||||
|
arg = arg.trim();
|
||||||
|
if (!arg.isEmpty()) extraArgs.add(arg);
|
||||||
|
}
|
||||||
|
options.setExtraJvmArgs(extraArgs);
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
public static class LaunchInfo {
|
public static class LaunchInfo {
|
||||||
private String instanceName;
|
private String instanceName;
|
||||||
private List<String> command;
|
private List<String> command;
|
||||||
|
|||||||
@@ -26,14 +26,12 @@ public class AuthManager {
|
|||||||
private static volatile AuthSession session = null;
|
private static volatile AuthSession session = null;
|
||||||
private static volatile UserInfo userInfo = null;
|
private static volatile UserInfo userInfo = null;
|
||||||
|
|
||||||
// === Роли ===
|
|
||||||
public static final int ROLE_USER = 0;
|
public static final int ROLE_USER = 0;
|
||||||
public static final int ROLE_PASS_HOLDER = 1;
|
public static final int ROLE_PASS_HOLDER = 1;
|
||||||
public static final int ROLE_MODERATOR = 2;
|
public static final int ROLE_MODERATOR = 2;
|
||||||
public static final int ROLE_ELDER = 3;
|
public static final int ROLE_ELDER = 3;
|
||||||
public static final int ROLE_CREATOR = 4;
|
public static final int ROLE_CREATOR = 4;
|
||||||
|
|
||||||
// === Права доступа ===
|
|
||||||
public static final String PERM_VIEW_PACKS = "view_packs";
|
public static final String PERM_VIEW_PACKS = "view_packs";
|
||||||
public static final String PERM_DOWNLOAD_PACK = "download_pack";
|
public static final String PERM_DOWNLOAD_PACK = "download_pack";
|
||||||
|
|
||||||
@@ -56,7 +54,6 @@ public class AuthManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== АВТОРИЗАЦИЯ ======================
|
|
||||||
public static AuthResult login(String username, String password) {
|
public static AuthResult login(String username, String password) {
|
||||||
return authRequest("/auth/login", username, password);
|
return authRequest("/auth/login", username, password);
|
||||||
}
|
}
|
||||||
@@ -77,13 +74,13 @@ public class AuthManager {
|
|||||||
userInfo = fetchUserInfo();
|
userInfo = fetchUserInfo();
|
||||||
return AuthResult.ok();
|
return AuthResult.ok();
|
||||||
} else if (resp.statusCode() == 422) {
|
} else if (resp.statusCode() == 422) {
|
||||||
return AuthResult.fail("Ошибка валидации: " + extractError(resp.body()));
|
return AuthResult.fail("Validation error: " + extractError(resp.body()));
|
||||||
} else {
|
} else {
|
||||||
return AuthResult.fail(extractError(resp.body()));
|
return AuthResult.fail(extractError(resp.body()));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return AuthResult.fail("Ошибка соединения: " + e.getMessage());
|
return AuthResult.fail("Connection error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,16 +146,14 @@ public class AuthManager {
|
|||||||
Files.createDirectories(AUTH_FILE.getParent());
|
Files.createDirectories(AUTH_FILE.getParent());
|
||||||
Files.writeString(AUTH_FILE, GSON.toJson(session));
|
Files.writeString(AUTH_FILE, GSON.toJson(session));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.err.println(ZAnsi.yellow("Не удалось сохранить сессию: " + e.getMessage()));
|
System.err.println(ZAnsi.yellow("Failed to save session: " + e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== ПОЛУЧЕНИЕ ИНФОРМАЦИИ О ПОЛЬЗОВАТЕЛЕ ====================
|
|
||||||
private static UserInfo fetchUserInfo() {
|
private static UserInfo fetchUserInfo() {
|
||||||
if (!isLoggedIn() || session.accessToken == null) return null;
|
if (!isLoggedIn() || session.accessToken == null) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Используем существующий метод ZHttpClient.get() + вручную добавляем токен
|
|
||||||
java.net.HttpURLConnection conn = null;
|
java.net.HttpURLConnection conn = null;
|
||||||
try {
|
try {
|
||||||
URL url = new URL(ZHttpClient.getBaseUrl() + "/admin/me");
|
URL url = new URL(ZHttpClient.getBaseUrl() + "/admin/me");
|
||||||
@@ -185,12 +180,11 @@ public class AuthManager {
|
|||||||
if (conn != null) conn.disconnect();
|
if (conn != null) conn.disconnect();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("Не удалось получить UserInfo: " + e.getMessage());
|
System.err.println("Failed to get UserInfo: " + e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== ПРОВЕРКИ ПРАВ ====================
|
|
||||||
public static boolean hasPass() {
|
public static boolean hasPass() {
|
||||||
if (userInfo != null) return userInfo.has_pass;
|
if (userInfo != null) return userInfo.has_pass;
|
||||||
return getRole() >= ROLE_PASS_HOLDER;
|
return getRole() >= ROLE_PASS_HOLDER;
|
||||||
@@ -200,14 +194,14 @@ public class AuthManager {
|
|||||||
if (userInfo != null && userInfo.permissions != null) {
|
if (userInfo != null && userInfo.permissions != null) {
|
||||||
return userInfo.permissions.contains(PERM_VIEW_PACKS);
|
return userInfo.permissions.contains(PERM_VIEW_PACKS);
|
||||||
}
|
}
|
||||||
return hasPass(); // fallback для старых аккаунтов
|
return hasPass();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean canDownloadPacks() {
|
public static boolean canDownloadPacks() {
|
||||||
if (userInfo != null && userInfo.permissions != null) {
|
if (userInfo != null && userInfo.permissions != null) {
|
||||||
return userInfo.permissions.contains(PERM_DOWNLOAD_PACK);
|
return userInfo.permissions.contains(PERM_DOWNLOAD_PACK);
|
||||||
}
|
}
|
||||||
return hasPass(); // fallback
|
return hasPass();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getRole() {
|
public static int getRole() {
|
||||||
@@ -221,7 +215,6 @@ public class AuthManager {
|
|||||||
return "USER";
|
return "USER";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== POST ======================
|
|
||||||
private static SimpleHttpResponse post(String endpoint, String jsonBody) throws Exception {
|
private static SimpleHttpResponse post(String endpoint, String jsonBody) throws Exception {
|
||||||
String fullUrl = ZHttpClient.getBaseUrl() + endpoint;
|
String fullUrl = ZHttpClient.getBaseUrl() + endpoint;
|
||||||
HttpURLConnection conn = null;
|
HttpURLConnection conn = null;
|
||||||
@@ -291,24 +284,23 @@ public class AuthManager {
|
|||||||
JsonObject json = JsonParser.parseString(response).getAsJsonObject();
|
JsonObject json = JsonParser.parseString(response).getAsJsonObject();
|
||||||
return json.has("has_active") && json.get("has_active").getAsBoolean();
|
return json.has("has_active") && json.get("has_active").getAsBoolean();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println(ZAnsi.red("Не удалось проверить проходки: ") + e.getMessage());
|
System.err.println(ZAnsi.red("Failed to check pass: ") + e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getPassStatus() {
|
public static String getPassStatus() {
|
||||||
if (!isLoggedIn()) return "Не авторизован";
|
if (!isLoggedIn()) return "Not logged in";
|
||||||
try {
|
try {
|
||||||
String response = ZHttpClient.get("/auth/pass/my");
|
String response = ZHttpClient.get("/auth/pass/my");
|
||||||
JsonObject json = JsonParser.parseString(response).getAsJsonObject();
|
JsonObject json = JsonParser.parseString(response).getAsJsonObject();
|
||||||
boolean hasActive = json.has("has_active") && json.get("has_active").getAsBoolean();
|
boolean hasActive = json.has("has_active") && json.get("has_active").getAsBoolean();
|
||||||
return hasActive ? "Есть активная проходка" : "Проходка отсутствует";
|
return hasActive ? "Active pass" : "No pass";
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return "Ошибка проверки";
|
return "Check error";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== ВНУТРЕННИЕ КЛАССЫ ======================
|
|
||||||
public static class AuthSession {
|
public static class AuthSession {
|
||||||
@SerializedName("access_token") public String accessToken;
|
@SerializedName("access_token") public String accessToken;
|
||||||
@SerializedName("refresh_token") public String refreshToken;
|
@SerializedName("refresh_token") public String refreshToken;
|
||||||
@@ -351,7 +343,6 @@ public class AuthManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== ВСПОМОГАТЕЛЬНЫЙ КЛАСС ======================
|
|
||||||
class SimpleHttpResponse {
|
class SimpleHttpResponse {
|
||||||
final int statusCode;
|
final int statusCode;
|
||||||
final String body;
|
final String body;
|
||||||
|
|||||||
@@ -33,12 +33,11 @@ public class LaunchMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== ZERNMC BUILD ======================
|
|
||||||
private void showZernMCOnly() throws Exception {
|
private void showZernMCOnly() throws Exception {
|
||||||
while (true) {
|
while (true) {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.header("=== ZernMC Private Launcher ==="));
|
System.out.println(ZAnsi.header("=== ZernMC Private Launcher ==="));
|
||||||
System.out.println(ZAnsi.cyan("Доступны только серверные сборки"));
|
System.out.println(ZAnsi.cyan("Server packs only"));
|
||||||
|
|
||||||
if (!awaitActivePass()) {
|
if (!awaitActivePass()) {
|
||||||
return;
|
return;
|
||||||
@@ -48,13 +47,13 @@ public class LaunchMenu {
|
|||||||
List<ServerPack> availablePacks = tempDownloader.getAvailablePacks();
|
List<ServerPack> availablePacks = tempDownloader.getAvailablePacks();
|
||||||
|
|
||||||
if (availablePacks.isEmpty()) {
|
if (availablePacks.isEmpty()) {
|
||||||
System.out.println(ZAnsi.yellow("На данный момент нет доступных сборок на сервере."));
|
System.out.println(ZAnsi.yellow("No packs available on the server."));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> options = availablePacks.stream()
|
List<String> options = availablePacks.stream()
|
||||||
.map(p -> String.format("%s [%s + %s v%d] — %d файлов",
|
.map(p -> String.format("%s [%s + %s v%d] - %d files",
|
||||||
p.getName(),
|
p.getName(),
|
||||||
p.getMinecraftVersion(),
|
p.getMinecraftVersion(),
|
||||||
p.getLoaderType(),
|
p.getLoaderType(),
|
||||||
@@ -62,9 +61,9 @@ public class LaunchMenu {
|
|||||||
p.getFilesCount()))
|
p.getFilesCount()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
options.add("Назад в главное меню");
|
options.add("Back to main menu");
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu("Выберите сборку", options);
|
ArrowMenu menu = new ArrowMenu("Select a pack", options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
|
|
||||||
if (choice == -1 || choice == options.size() - 1) return;
|
if (choice == -1 || choice == options.size() - 1) return;
|
||||||
@@ -76,25 +75,25 @@ public class LaunchMenu {
|
|||||||
|
|
||||||
private boolean awaitActivePass() throws Exception {
|
private boolean awaitActivePass() throws Exception {
|
||||||
if (AuthManager.hasActivePass()) {
|
if (AuthManager.hasActivePass()) {
|
||||||
System.out.println(ZAnsi.brightGreen("✓ Активная проходка подтверждена"));
|
System.out.println(ZAnsi.brightGreen("✓ Active pass confirmed"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.brightRed("У вас нет активной проходки!"));
|
System.out.println(ZAnsi.brightRed("You don't have an active pass!"));
|
||||||
System.out.println(ZAnsi.white("Для доступа к сборкам ZernMC требуется активная проходка."));
|
System.out.println(ZAnsi.white("Access to ZernMC packs requires an active pass."));
|
||||||
System.out.println();
|
System.out.println();
|
||||||
|
|
||||||
openActivationWebsite();
|
openActivationWebsite();
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("Ожидаем активацию проходки... (проверка каждые 10 секунд)"));
|
System.out.println(ZAnsi.cyan("Waiting for pass activation... (checking every 10 seconds)"));
|
||||||
System.out.println(ZAnsi.white("Нажмите Enter для отмены"));
|
System.out.println(ZAnsi.white("Press Enter to cancel"));
|
||||||
|
|
||||||
for (int i = 0; i < 60; i++) {
|
for (int i = 0; i < 60; i++) {
|
||||||
try {
|
try {
|
||||||
if (System.in.available() > 0) {
|
if (System.in.available() > 0) {
|
||||||
Input.readLine();
|
Input.readLine();
|
||||||
System.out.println(ZAnsi.yellow("\nОжидание отменено."));
|
System.out.println(ZAnsi.yellow("\nWaiting cancelled."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
@@ -102,7 +101,7 @@ public class LaunchMenu {
|
|||||||
Thread.sleep(10000);
|
Thread.sleep(10000);
|
||||||
|
|
||||||
if (AuthManager.hasActivePass()) {
|
if (AuthManager.hasActivePass()) {
|
||||||
System.out.println(ZAnsi.brightGreen("\n✓ Проходка успешно активирована!"));
|
System.out.println(ZAnsi.brightGreen("\n✓ Pass activated successfully!"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,43 +109,42 @@ public class LaunchMenu {
|
|||||||
if ((i + 1) % 6 == 0) System.out.println();
|
if ((i + 1) % 6 == 0) System.out.println();
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println(ZAnsi.brightRed("\n\nВремя ожидания истекло."));
|
System.out.println(ZAnsi.brightRed("\n\nWaiting time expired."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openActivationWebsite() {
|
private void openActivationWebsite() {
|
||||||
//String url = "https://launcher.ru.zernmc.ru/activate-pass";
|
|
||||||
String url = ZHttpClient.getBaseUrl() + "/activate-pass";
|
String url = ZHttpClient.getBaseUrl() + "/activate-pass";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
|
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
|
||||||
Desktop.getDesktop().browse(new URI(url));
|
Desktop.getDesktop().browse(new URI(url));
|
||||||
System.out.println(ZAnsi.cyan("Браузер открыт: " + url));
|
System.out.println(ZAnsi.cyan("Browser opened: " + url));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.yellow("Не удалось открыть браузер автоматически."));
|
System.out.println(ZAnsi.yellow("Could not open browser automatically."));
|
||||||
System.out.println(ZAnsi.white("Откройте вручную: " + url));
|
System.out.println(ZAnsi.white("Open manually: " + url));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.brightRed("Ошибка открытия браузера: " + e.getMessage()));
|
System.out.println(ZAnsi.brightRed("Error opening browser: " + e.getMessage()));
|
||||||
System.out.println(ZAnsi.white("Ссылка: " + url));
|
System.out.println(ZAnsi.white("Link: " + url));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void installAndRunServerPack(ServerPack selected) throws Exception {
|
private void installAndRunServerPack(ServerPack selected) throws Exception {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.header("Установка сборки: " + selected.getName()));
|
System.out.println(ZAnsi.header("Installing pack: " + selected.getName()));
|
||||||
|
|
||||||
System.out.println(ZAnsi.white(" Minecraft: ") + selected.getMinecraftVersion());
|
System.out.println(ZAnsi.white(" Minecraft: ") + selected.getMinecraftVersion());
|
||||||
System.out.println(ZAnsi.white(" Лоадер: ") + selected.getLoaderType() +
|
System.out.println(ZAnsi.white(" Loader: ") + selected.getLoaderType() +
|
||||||
(selected.getLoaderVersion() != null ? " " + selected.getLoaderVersion() : ""));
|
(selected.getLoaderVersion() != null ? " " + selected.getLoaderVersion() : ""));
|
||||||
System.out.println(ZAnsi.white(" Версия: v") + selected.getVersion());
|
System.out.println(ZAnsi.white(" Version: v") + selected.getVersion());
|
||||||
System.out.println(ZAnsi.white(" Файлов: ") + selected.getFilesCount());
|
System.out.println(ZAnsi.white(" Files: ") + selected.getFilesCount());
|
||||||
|
|
||||||
String localName = askPackName();
|
String localName = askPackName();
|
||||||
if (localName == null) return;
|
if (localName == null) return;
|
||||||
|
|
||||||
if (InstanceManager.getInstance(localName) != null) {
|
if (InstanceManager.getInstance(localName) != null) {
|
||||||
System.out.println(ZAnsi.brightRed("Сборка с таким именем уже существует!"));
|
System.out.println(ZAnsi.brightRed("A pack with this name already exists!"));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -158,18 +156,17 @@ public class LaunchMenu {
|
|||||||
boolean success = packDownloader.installOrUpdatePack(selected.getName(), selected);
|
boolean success = packDownloader.installOrUpdatePack(selected.getName(), selected);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
System.out.println(ZAnsi.brightRed("\n[FAIL] Не удалось установить сборку."));
|
System.out.println(ZAnsi.brightRed("\n[FAIL] Could not install the pack."));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println(ZAnsi.brightGreen("\n[OK] Сборка '" + localName + "' успешно установлена!"));
|
System.out.println(ZAnsi.brightGreen("\n[OK] Pack '" + localName + "' installed successfully!"));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
|
|
||||||
launchExistingInstance(newInstance);
|
launchExistingInstance(newInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== GLOBAL BUILD ======================
|
|
||||||
private void showGlobal() throws Exception {
|
private void showGlobal() throws Exception {
|
||||||
while (true) {
|
while (true) {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
@@ -179,10 +176,10 @@ public class LaunchMenu {
|
|||||||
.map(Instance::toString)
|
.map(Instance::toString)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
options.add("Установить новую сборку");
|
options.add("Install new pack");
|
||||||
options.add("Назад в главное меню");
|
options.add("Back to main menu");
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu("Управление сборками", options);
|
ArrowMenu menu = new ArrowMenu("Manage packs", options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
|
|
||||||
if (choice == -1 || choice == options.size() - 1) break;
|
if (choice == -1 || choice == options.size() - 1) break;
|
||||||
@@ -201,13 +198,13 @@ public class LaunchMenu {
|
|||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
|
|
||||||
List<String> options = List.of(
|
List<String> options = List.of(
|
||||||
"Установить сборку с сервера ZernMC",
|
"Install pack from ZernMC server",
|
||||||
"Установить Vanilla Minecraft",
|
"Install Vanilla Minecraft",
|
||||||
"Создать сборку вручную (Fabric/Forge)",
|
"Create custom pack (Fabric/Forge)",
|
||||||
"Назад"
|
"Back"
|
||||||
);
|
);
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu("Установка новой сборки", options);
|
ArrowMenu menu = new ArrowMenu("Install new pack", options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
|
|
||||||
if (choice == -1 || choice == 3) return;
|
if (choice == -1 || choice == 3) return;
|
||||||
@@ -223,28 +220,28 @@ public class LaunchMenu {
|
|||||||
if (!awaitActivePass()) return;
|
if (!awaitActivePass()) return;
|
||||||
|
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.cyan("Получение списка доступных сборок..."));
|
System.out.println(ZAnsi.cyan("Fetching available packs..."));
|
||||||
|
|
||||||
PackDownloader tempDownloader = new PackDownloader(null);
|
PackDownloader tempDownloader = new PackDownloader(null);
|
||||||
List<ServerPack> availablePacks = tempDownloader.getAvailablePacks();
|
List<ServerPack> availablePacks = tempDownloader.getAvailablePacks();
|
||||||
|
|
||||||
if (availablePacks.isEmpty()) {
|
if (availablePacks.isEmpty()) {
|
||||||
System.out.println(ZAnsi.yellow("Нет доступных сборок на сервере."));
|
System.out.println(ZAnsi.yellow("No packs available on the server."));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> options = availablePacks.stream()
|
List<String> options = availablePacks.stream()
|
||||||
.map(p -> String.format("%s [%s + %s v%d] — %d файлов",
|
.map(p -> String.format("%s [%s + %s v%d] - %d files",
|
||||||
p.getName(),
|
p.getName(),
|
||||||
p.getMinecraftVersion(),
|
p.getMinecraftVersion(),
|
||||||
p.getLoaderType(),
|
p.getLoaderType(),
|
||||||
p.getVersion(),
|
p.getVersion(),
|
||||||
p.getFilesCount()))
|
p.getFilesCount()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
options.add("Назад");
|
options.add("Back");
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu("Выберите сборку для установки", options);
|
ArrowMenu menu = new ArrowMenu("Select a pack to install", options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
|
|
||||||
if (choice == -1 || choice == options.size() - 1) return;
|
if (choice == -1 || choice == options.size() - 1) return;
|
||||||
@@ -252,14 +249,14 @@ public class LaunchMenu {
|
|||||||
ServerPack selected = availablePacks.get(choice);
|
ServerPack selected = availablePacks.get(choice);
|
||||||
|
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.header("Установка сборки: " + selected.getName()));
|
System.out.println(ZAnsi.header("Installing pack: " + selected.getName()));
|
||||||
|
|
||||||
System.out.print(ZAnsi.white("\nВведите название локальной сборки (Enter = имя пака): "));
|
System.out.print(ZAnsi.white("\nEnter local pack name (Enter = pack name): "));
|
||||||
String localName = Input.readLine().trim();
|
String localName = Input.readLine().trim();
|
||||||
if (localName.isEmpty()) localName = selected.getName();
|
if (localName.isEmpty()) localName = selected.getName();
|
||||||
|
|
||||||
if (InstanceManager.getInstance(localName) != null) {
|
if (InstanceManager.getInstance(localName) != null) {
|
||||||
System.out.println(ZAnsi.brightRed("Сборка с таким именем уже существует!"));
|
System.out.println(ZAnsi.brightRed("A pack with this name already exists!"));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -271,37 +268,36 @@ public class LaunchMenu {
|
|||||||
boolean success = packDownloader.installOrUpdatePack(selected.getName(), selected);
|
boolean success = packDownloader.installOrUpdatePack(selected.getName(), selected);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
System.out.println(ZAnsi.brightGreen("\n[OK] Сборка '" + localName + "' успешно установлена!"));
|
System.out.println(ZAnsi.brightGreen("\n[OK] Pack '" + localName + "' installed successfully!"));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed("\n[FAIL] Не удалось установить сборку."));
|
System.out.println(ZAnsi.brightRed("\n[FAIL] Could not install the pack."));
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== manageInstance — полностью восстановлен ======================
|
|
||||||
private void manageInstance(Instance instance) throws Exception {
|
private void manageInstance(Instance instance) throws Exception {
|
||||||
while (true) {
|
while (true) {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.header("Управление сборкой: " + instance.getName()));
|
System.out.println(ZAnsi.header("Managing pack: " + instance.getName()));
|
||||||
System.out.println(ZAnsi.white("Версия: " + instance.getMinecraftVersion()));
|
System.out.println(ZAnsi.white("Version: " + instance.getMinecraftVersion()));
|
||||||
System.out.println(ZAnsi.white("Лоадер: " + instance.getLoaderType() +
|
System.out.println(ZAnsi.white("Loader: " + instance.getLoaderType() +
|
||||||
(instance.getLoaderVersion() != null ? " " + instance.getLoaderVersion() : "")));
|
(instance.getLoaderVersion() != null ? " " + instance.getLoaderVersion() : "")));
|
||||||
|
|
||||||
if (instance.isServerPack()) {
|
if (instance.isServerPack()) {
|
||||||
System.out.println(ZAnsi.green("Серверная сборка: v" + instance.getServerVersion()));
|
System.out.println(ZAnsi.green("Server pack: v" + instance.getServerVersion()));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> options = new ArrayList<>();
|
List<String> options = new ArrayList<>();
|
||||||
options.add("Запустить сборку");
|
options.add("Launch pack");
|
||||||
if (instance.isServerPack()) {
|
if (instance.isServerPack()) {
|
||||||
options.add("Проверить обновления");
|
options.add("Check for updates");
|
||||||
}
|
}
|
||||||
options.add("Изменить версию лоадера");
|
options.add("Change loader version");
|
||||||
options.add("Удалить сборку");
|
options.add("Delete pack");
|
||||||
options.add("Назад");
|
options.add("Back");
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu("Действия", options);
|
ArrowMenu menu = new ArrowMenu("Actions", options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
|
|
||||||
if (choice == -1 || choice == options.size() - 1) return;
|
if (choice == -1 || choice == options.size() - 1) return;
|
||||||
@@ -329,40 +325,40 @@ public class LaunchMenu {
|
|||||||
|
|
||||||
private void checkAndUpdateServerPack(Instance instance) throws Exception {
|
private void checkAndUpdateServerPack(Instance instance) throws Exception {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.cyan("Проверка обновлений для " + instance.getName()));
|
System.out.println(ZAnsi.cyan("Checking updates for " + instance.getName()));
|
||||||
|
|
||||||
PackDownloader downloader = new PackDownloader(instance);
|
PackDownloader downloader = new PackDownloader(instance);
|
||||||
boolean hasUpdate = downloader.checkForUpdates(instance.getServerPackName());
|
boolean hasUpdate = downloader.checkForUpdates(instance.getServerPackName());
|
||||||
|
|
||||||
if (!hasUpdate) {
|
if (!hasUpdate) {
|
||||||
System.out.println(ZAnsi.green("Сборка актуальна (v" + instance.getServerVersion() + ")"));
|
System.out.println(ZAnsi.green("Pack is up to date (v" + instance.getServerVersion() + ")"));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println(ZAnsi.brightYellow("Доступно обновление!"));
|
System.out.println(ZAnsi.brightYellow("Update available!"));
|
||||||
if (Input.confirm("Обновить сборку")) {
|
if (Input.confirm("Update pack")) {
|
||||||
boolean success = downloader.updatePack(instance.getServerPackName());
|
boolean success = downloader.updatePack(instance.getServerPackName());
|
||||||
if (success) {
|
if (success) {
|
||||||
System.out.println(ZAnsi.brightGreen("Сборка успешно обновлена!"));
|
System.out.println(ZAnsi.brightGreen("Pack updated successfully!"));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed("Не удалось обновить сборку."));
|
System.out.println(ZAnsi.brightRed("Failed to update pack."));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.yellow("Обновление отменено."));
|
System.out.println(ZAnsi.yellow("Update cancelled."));
|
||||||
}
|
}
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changeLoaderVersion(Instance instance) throws Exception {
|
private void changeLoaderVersion(Instance instance) throws Exception {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.cyan("Изменение версии лоадера для " + instance.getName()));
|
System.out.println(ZAnsi.cyan("Changing loader version for " + instance.getName()));
|
||||||
|
|
||||||
String currentLoader = instance.getLoaderType();
|
String currentLoader = instance.getLoaderType();
|
||||||
String mcVersion = instance.getMinecraftVersion();
|
String mcVersion = instance.getMinecraftVersion();
|
||||||
|
|
||||||
if ("vanilla".equalsIgnoreCase(currentLoader)) {
|
if ("vanilla".equalsIgnoreCase(currentLoader)) {
|
||||||
System.out.println(ZAnsi.yellow("Это vanilla сборка. Нельзя изменить лоадер."));
|
System.out.println(ZAnsi.yellow("This is a vanilla instance. Cannot change loader."));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -378,7 +374,7 @@ public class LaunchMenu {
|
|||||||
|
|
||||||
if (newLoaderVersion == null) return;
|
if (newLoaderVersion == null) return;
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("Переустановка лоадера " + currentLoader + " -> " + newLoaderVersion + "..."));
|
System.out.println(ZAnsi.cyan("Reinstalling loader " + currentLoader + " -> " + newLoaderVersion + "..."));
|
||||||
|
|
||||||
MinecraftLib lib = new MinecraftLib(instance);
|
MinecraftLib lib = new MinecraftLib(instance);
|
||||||
boolean success;
|
boolean success;
|
||||||
@@ -393,12 +389,12 @@ public class LaunchMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
System.out.println(ZAnsi.brightGreen("Версия лоадера успешно изменена!"));
|
System.out.println(ZAnsi.brightGreen("Loader version changed successfully!"));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed("Не удалось изменить версию лоадера."));
|
System.out.println(ZAnsi.brightRed("Failed to change loader version."));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.brightRed("Ошибка при смене лоадера: " + e.getMessage()));
|
System.out.println(ZAnsi.brightRed("Error changing loader: " + e.getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
@@ -408,12 +404,12 @@ public class LaunchMenu {
|
|||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
|
|
||||||
List<String> confirmOptions = List.of(
|
List<String> confirmOptions = List.of(
|
||||||
"Да, удалить сборку",
|
"Yes, delete pack",
|
||||||
"Нет, отменить"
|
"No, cancel"
|
||||||
);
|
);
|
||||||
|
|
||||||
ArrowMenu confirmMenu = new ArrowMenu(
|
ArrowMenu confirmMenu = new ArrowMenu(
|
||||||
"Вы действительно хотите удалить сборку '" + instance.getName() + "'?",
|
"Are you sure you want to delete '" + instance.getName() + "'?",
|
||||||
confirmOptions
|
confirmOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -422,12 +418,12 @@ public class LaunchMenu {
|
|||||||
if (choice == 0) {
|
if (choice == 0) {
|
||||||
boolean deleted = InstanceManager.deleteInstance(instance.getName());
|
boolean deleted = InstanceManager.deleteInstance(instance.getName());
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
System.out.println(ZAnsi.brightGreen("Сборка '" + instance.getName() + "' успешно удалена."));
|
System.out.println(ZAnsi.brightGreen("Pack '" + instance.getName() + "' deleted successfully."));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed("Не удалось удалить сборку."));
|
System.out.println(ZAnsi.brightRed("Failed to delete pack."));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.yellow("Удаление отменено."));
|
System.out.println(ZAnsi.yellow("Deletion cancelled."));
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
@@ -436,16 +432,20 @@ public class LaunchMenu {
|
|||||||
private void launchExistingInstance(Instance instance) {
|
private void launchExistingInstance(Instance instance) {
|
||||||
if (instance.isServerPack() && !AuthManager.hasActivePass()) {
|
if (instance.isServerPack() && !AuthManager.hasActivePass()) {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.brightRed("Для запуска серверной сборки требуется активная проходка!"));
|
System.out.println(ZAnsi.brightRed("Launching a server pack requires an active pass!"));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.brightGreen("Запуск сборки: " + instance.getName()));
|
System.out.println(ZAnsi.brightGreen("Launching pack: " + instance.getName()));
|
||||||
|
|
||||||
MinecraftLib lib = new MinecraftLib(instance);
|
MinecraftLib lib = new MinecraftLib(instance);
|
||||||
LaunchOptions options = new LaunchOptions();
|
LaunchOptions options = new LaunchOptions();
|
||||||
|
options.setMaxMemory(Config.getMaxMemory());
|
||||||
|
options.setWidth(Config.getWindowWidth());
|
||||||
|
options.setHeight(Config.getWindowHeight());
|
||||||
|
options.setJavaPath(Config.getJavaPath());
|
||||||
|
|
||||||
options.setUsername(AuthManager.getUsername());
|
options.setUsername(AuthManager.getUsername());
|
||||||
options.setUuid(AuthManager.getUuid());
|
options.setUuid(AuthManager.getUuid());
|
||||||
@@ -454,20 +454,18 @@ public class LaunchMenu {
|
|||||||
try {
|
try {
|
||||||
lib.launch(options);
|
lib.launch(options);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.brightRed("Ошибка при запуске: " + e.getMessage()));
|
System.out.println(ZAnsi.brightRed("Error launching: " + e.getMessage()));
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== Остальные вспомогательные методы ======================
|
|
||||||
|
|
||||||
private String askPackName() {
|
private String askPackName() {
|
||||||
System.out.print(ZAnsi.white("\nВведите название новой сборки: "));
|
System.out.print(ZAnsi.white("\nEnter new pack name: "));
|
||||||
String name = Input.readLine().trim();
|
String name = Input.readLine().trim();
|
||||||
if (name.isEmpty()) {
|
if (name.isEmpty()) {
|
||||||
System.out.println(ZAnsi.yellow("Отменено."));
|
System.out.println(ZAnsi.yellow("Cancelled."));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return name;
|
return name;
|
||||||
@@ -475,7 +473,7 @@ public class LaunchMenu {
|
|||||||
|
|
||||||
private void createVanillaInstance() throws Exception {
|
private void createVanillaInstance() throws Exception {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.cyan("Получение списка версий Minecraft..."));
|
System.out.println(ZAnsi.cyan("Fetching Minecraft versions..."));
|
||||||
|
|
||||||
VersionInstaller versionInstaller = new VersionInstaller(null);
|
VersionInstaller versionInstaller = new VersionInstaller(null);
|
||||||
List<MinecraftVersion> allVersions = versionInstaller.getAvailableVersions();
|
List<MinecraftVersion> allVersions = versionInstaller.getAvailableVersions();
|
||||||
@@ -483,9 +481,9 @@ public class LaunchMenu {
|
|||||||
List<String> versionOptions = allVersions.stream()
|
List<String> versionOptions = allVersions.stream()
|
||||||
.map(v -> v.getId() + " (" + v.getType() + ")")
|
.map(v -> v.getId() + " (" + v.getType() + ")")
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
versionOptions.add("Назад");
|
versionOptions.add("Back");
|
||||||
|
|
||||||
ArrowMenu versionMenu = new ArrowMenu("Выбор версии Minecraft", versionOptions);
|
ArrowMenu versionMenu = new ArrowMenu("Select Minecraft version", versionOptions);
|
||||||
int versionChoice = versionMenu.show();
|
int versionChoice = versionMenu.show();
|
||||||
|
|
||||||
if (versionChoice == -1 || versionChoice == versionOptions.size() - 1) return;
|
if (versionChoice == -1 || versionChoice == versionOptions.size() - 1) return;
|
||||||
@@ -497,7 +495,7 @@ public class LaunchMenu {
|
|||||||
if (packName == null) return;
|
if (packName == null) return;
|
||||||
|
|
||||||
if (InstanceManager.getInstance(packName) != null) {
|
if (InstanceManager.getInstance(packName) != null) {
|
||||||
System.out.println(ZAnsi.brightRed("Сборка с таким именем уже существует!"));
|
System.out.println(ZAnsi.brightRed("A pack with this name already exists!"));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -509,9 +507,9 @@ public class LaunchMenu {
|
|||||||
boolean success = lib.installMinecraft(mcVersion);
|
boolean success = lib.installMinecraft(mcVersion);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
System.out.println(ZAnsi.brightGreen("\n[OK] Vanilla сборка '" + packName + "' успешно создана!"));
|
System.out.println(ZAnsi.brightGreen("\n[OK] Vanilla pack '" + packName + "' created successfully!"));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed("\n[FAIL] Не удалось создать сборку."));
|
System.out.println(ZAnsi.brightRed("\n[FAIL] Failed to create pack."));
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
@@ -519,7 +517,7 @@ public class LaunchMenu {
|
|||||||
|
|
||||||
private void createCustomInstance() throws Exception {
|
private void createCustomInstance() throws Exception {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.cyan("Получение списка версий Minecraft..."));
|
System.out.println(ZAnsi.cyan("Fetching Minecraft versions..."));
|
||||||
|
|
||||||
VersionInstaller versionInstaller = new VersionInstaller(null);
|
VersionInstaller versionInstaller = new VersionInstaller(null);
|
||||||
List<MinecraftVersion> allVersions = versionInstaller.getAvailableVersions();
|
List<MinecraftVersion> allVersions = versionInstaller.getAvailableVersions();
|
||||||
@@ -527,9 +525,9 @@ public class LaunchMenu {
|
|||||||
List<String> versionOptions = allVersions.stream()
|
List<String> versionOptions = allVersions.stream()
|
||||||
.map(v -> v.getId() + " (" + v.getType() + ")")
|
.map(v -> v.getId() + " (" + v.getType() + ")")
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
versionOptions.add("Назад");
|
versionOptions.add("Back");
|
||||||
|
|
||||||
ArrowMenu versionMenu = new ArrowMenu("Выбор версии Minecraft", versionOptions);
|
ArrowMenu versionMenu = new ArrowMenu("Select Minecraft version", versionOptions);
|
||||||
int versionChoice = versionMenu.show();
|
int versionChoice = versionMenu.show();
|
||||||
|
|
||||||
if (versionChoice == -1 || versionChoice == versionOptions.size() - 1) return;
|
if (versionChoice == -1 || versionChoice == versionOptions.size() - 1) return;
|
||||||
@@ -538,7 +536,7 @@ public class LaunchMenu {
|
|||||||
String mcVersion = selectedMc.getId();
|
String mcVersion = selectedMc.getId();
|
||||||
|
|
||||||
List<String> loaderOptions = buildLoaderOptions(mcVersion);
|
List<String> loaderOptions = buildLoaderOptions(mcVersion);
|
||||||
ArrowMenu loaderMenu = new ArrowMenu("Выбор модлоадера для " + mcVersion, loaderOptions);
|
ArrowMenu loaderMenu = new ArrowMenu("Select mod loader for " + mcVersion, loaderOptions);
|
||||||
int loaderChoice = loaderMenu.show();
|
int loaderChoice = loaderMenu.show();
|
||||||
|
|
||||||
if (loaderChoice == -1 || loaderChoice == loaderOptions.size() - 1) return;
|
if (loaderChoice == -1 || loaderChoice == loaderOptions.size() - 1) return;
|
||||||
@@ -574,7 +572,7 @@ public class LaunchMenu {
|
|||||||
if (packName == null) return;
|
if (packName == null) return;
|
||||||
|
|
||||||
if (InstanceManager.getInstance(packName) != null) {
|
if (InstanceManager.getInstance(packName) != null) {
|
||||||
System.out.println(ZAnsi.brightRed("Сборка с таким именем уже существует!"));
|
System.out.println(ZAnsi.brightRed("A pack with this name already exists!"));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -594,9 +592,9 @@ public class LaunchMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
System.out.println(ZAnsi.brightGreen("\n[OK] Сборка '" + packName + "' успешно установлена!"));
|
System.out.println(ZAnsi.brightGreen("\n[OK] Pack '" + packName + "' installed successfully!"));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed("\n[FAIL] Не удалось установить сборку."));
|
System.out.println(ZAnsi.brightRed("\n[FAIL] Failed to install pack."));
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
@@ -609,7 +607,7 @@ public class LaunchMenu {
|
|||||||
if (isNeoForgeSupported(mcVersion)) options.add("NeoForge");
|
if (isNeoForgeSupported(mcVersion)) options.add("NeoForge");
|
||||||
if (isForgeSupported(mcVersion)) options.add("Forge");
|
if (isForgeSupported(mcVersion)) options.add("Forge");
|
||||||
options.add("Vanilla");
|
options.add("Vanilla");
|
||||||
options.add("Назад");
|
options.add("Back");
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
@@ -631,16 +629,16 @@ public class LaunchMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String askFabricLoaderVersion() throws Exception {
|
private String askFabricLoaderVersion() throws Exception {
|
||||||
System.out.println(ZAnsi.cyan("Получение списка версий Fabric Loader..."));
|
System.out.println(ZAnsi.cyan("Fetching Fabric Loader versions..."));
|
||||||
List<String> versions = ZHttpClient.getFabricLoaderVersions();
|
List<String> versions = ZHttpClient.getFabricLoaderVersions();
|
||||||
|
|
||||||
List<String> options = versions.stream()
|
List<String> options = versions.stream()
|
||||||
.limit(30)
|
.limit(30)
|
||||||
.map(v -> "Fabric Loader " + v)
|
.map(v -> "Fabric Loader " + v)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
options.add("Назад");
|
options.add("Back");
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu("Выбор версии Fabric Loader", options);
|
ArrowMenu menu = new ArrowMenu("Select Fabric Loader version", options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
|
|
||||||
if (choice == -1 || choice == options.size() - 1) return null;
|
if (choice == -1 || choice == options.size() - 1) return null;
|
||||||
@@ -648,7 +646,7 @@ public class LaunchMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String askForgeVersion(String mcVersion) throws Exception {
|
private String askForgeVersion(String mcVersion) throws Exception {
|
||||||
System.out.println(ZAnsi.cyan("Получение списка версий Forge для " + mcVersion + "..."));
|
System.out.println(ZAnsi.cyan("Fetching Forge versions for " + mcVersion + "..."));
|
||||||
|
|
||||||
List<String> allForgeVersions = getAllForgeVersions();
|
List<String> allForgeVersions = getAllForgeVersions();
|
||||||
|
|
||||||
@@ -658,7 +656,7 @@ public class LaunchMenu {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (compatibleVersions.isEmpty()) {
|
if (compatibleVersions.isEmpty()) {
|
||||||
System.out.println(ZAnsi.yellow("Не найдено совместимых версий Forge для " + mcVersion));
|
System.out.println(ZAnsi.yellow("No compatible Forge versions found for " + mcVersion));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -667,9 +665,9 @@ public class LaunchMenu {
|
|||||||
.limit(30)
|
.limit(30)
|
||||||
.map(v -> "Forge " + v)
|
.map(v -> "Forge " + v)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
options.add("Назад");
|
options.add("Back");
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu("Выбор версии Forge для " + mcVersion, options);
|
ArrowMenu menu = new ArrowMenu("Select Forge version for " + mcVersion, options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
|
|
||||||
if (choice == -1 || choice == options.size() - 1) return null;
|
if (choice == -1 || choice == options.size() - 1) return null;
|
||||||
@@ -698,7 +696,7 @@ public class LaunchMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String askNeoForgeVersion(String mcVersion) throws Exception {
|
private String askNeoForgeVersion(String mcVersion) throws Exception {
|
||||||
System.out.println(ZAnsi.cyan("Получение списка версий NeoForge для " + mcVersion + "..."));
|
System.out.println(ZAnsi.cyan("Fetching NeoForge versions for " + mcVersion + "..."));
|
||||||
|
|
||||||
List<String> allNeoForgeVersions = getAllNeoForgeVersions();
|
List<String> allNeoForgeVersions = getAllNeoForgeVersions();
|
||||||
|
|
||||||
@@ -707,7 +705,7 @@ public class LaunchMenu {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (compatibleVersions.isEmpty()) {
|
if (compatibleVersions.isEmpty()) {
|
||||||
System.out.println(ZAnsi.yellow("Не найдено совместимых версий NeoForge для " + mcVersion));
|
System.out.println(ZAnsi.yellow("No compatible NeoForge versions found for " + mcVersion));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -716,9 +714,9 @@ public class LaunchMenu {
|
|||||||
.limit(30)
|
.limit(30)
|
||||||
.map(v -> "NeoForge " + v)
|
.map(v -> "NeoForge " + v)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
options.add("Назад");
|
options.add("Back");
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu("Выбор версии NeoForge для " + mcVersion, options);
|
ArrowMenu menu = new ArrowMenu("Select NeoForge version for " + mcVersion, options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
|
|
||||||
if (choice == -1 || choice == options.size() - 1) return null;
|
if (choice == -1 || choice == options.size() - 1) return null;
|
||||||
@@ -760,7 +758,6 @@ public class LaunchMenu {
|
|||||||
index = end;
|
index = end;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Skip if one maven doesn't have the artifact
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,30 +10,20 @@ import me.sashegdev.zernmc.launcher.utils.ZAnsi;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
|
||||||
* Экран входа/регистрации.
|
|
||||||
* Показывается при старте лаунчера, если нет сохранённой сессии.
|
|
||||||
*
|
|
||||||
* show() возвращает true — пользователь вошёл/зарегистрировался
|
|
||||||
* false — пользователь выбрал выход из лаунчера
|
|
||||||
*/
|
|
||||||
public class LoginMenu {
|
public class LoginMenu {
|
||||||
|
|
||||||
/**
|
|
||||||
* Главный экран выбора действия.
|
|
||||||
*/
|
|
||||||
public boolean show() throws IOException {
|
public boolean show() throws IOException {
|
||||||
while (true) {
|
while (true) {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
printBanner();
|
printBanner();
|
||||||
|
|
||||||
List<String> options = List.of(
|
List<String> options = List.of(
|
||||||
"Войти в аккаунт",
|
"Sign In",
|
||||||
"Создать аккаунт",
|
"Create Account",
|
||||||
"Выйти из лаунчера"
|
"Exit Launcher"
|
||||||
);
|
);
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu("Добро пожаловать в ZernMC!", options);
|
ArrowMenu menu = new ArrowMenu("Welcome to ZernMC!", options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
|
|
||||||
if (choice == -1 || choice == 2) return false;
|
if (choice == -1 || choice == 2) return false;
|
||||||
@@ -45,62 +35,56 @@ public class LoginMenu {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (success) return true;
|
if (success) return true;
|
||||||
// Если не успех — покажем меню снова (ошибка уже напечатана внутри методов)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Показывается когда пользователь уже вошёл — предлагает выйти из аккаунта.
|
|
||||||
*/
|
|
||||||
public void showAccountMenu() throws IOException {
|
public void showAccountMenu() throws IOException {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
|
|
||||||
System.out.println(ZAnsi.header("=== Аккаунт ==="));
|
System.out.println(ZAnsi.header("=== Account ==="));
|
||||||
System.out.println();
|
System.out.println();
|
||||||
System.out.println(ZAnsi.white(" Игрок: ") + ZAnsi.brightGreen(AuthManager.getUsername()));
|
System.out.println(ZAnsi.white(" Player: ") + ZAnsi.brightGreen(AuthManager.getUsername()));
|
||||||
System.out.println(ZAnsi.white(" UUID: ") + ZAnsi.cyan(AuthManager.getUuid()));
|
System.out.println(ZAnsi.white(" UUID: ") + ZAnsi.cyan(AuthManager.getUuid()));
|
||||||
System.out.println();
|
System.out.println();
|
||||||
|
|
||||||
List<String> options = List.of(
|
List<String> options = List.of(
|
||||||
"Выйти из аккаунта",
|
"Log Out",
|
||||||
"Назад"
|
"Back"
|
||||||
);
|
);
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu("Управление аккаунтом", options);
|
ArrowMenu menu = new ArrowMenu("Account Management", options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
|
|
||||||
if (choice == 0) {
|
if (choice == 0) {
|
||||||
AuthManager.logout();
|
AuthManager.logout();
|
||||||
System.out.println(ZAnsi.yellow("Вы вышли из аккаунта."));
|
System.out.println(ZAnsi.yellow("Logged out."));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== ПРИВАТНЫЕ МЕТОДЫ ======================
|
|
||||||
|
|
||||||
private boolean doLogin() throws IOException {
|
private boolean doLogin() throws IOException {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
printBanner();
|
printBanner();
|
||||||
System.out.println(ZAnsi.cyan(" [ Вход в аккаунт ]"));
|
System.out.println(ZAnsi.cyan(" [ Sign In ]"));
|
||||||
System.out.println();
|
System.out.println();
|
||||||
|
|
||||||
String username = Input.readLine(ZAnsi.white(" Имя пользователя: "));
|
String username = Input.readLine(ZAnsi.white(" Username: "));
|
||||||
if (username.isEmpty()) return false;
|
if (username.isEmpty()) return false;
|
||||||
|
|
||||||
String password = readPassword(" Пароль: ");
|
String password = readPassword(" Password: ");
|
||||||
if (password.isEmpty()) return false;
|
if (password.isEmpty()) return false;
|
||||||
|
|
||||||
System.out.println();
|
System.out.println();
|
||||||
System.out.print(ZAnsi.cyan(" Выполняем вход..."));
|
System.out.print(ZAnsi.cyan(" Signing in..."));
|
||||||
|
|
||||||
AuthResult result = AuthManager.login(username, password);
|
AuthResult result = AuthManager.login(username, password);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
System.out.println("\r" + ZAnsi.brightGreen(" Добро пожаловать, " + AuthManager.getUsername() + "! "));
|
System.out.println("\r" + ZAnsi.brightGreen(" Welcome, " + AuthManager.getUsername() + "! "));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
System.out.println("\r" + ZAnsi.brightRed(" Ошибка: " + result.error + " "));
|
System.out.println("\r" + ZAnsi.brightRed(" Error: " + result.error + " "));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -109,45 +93,41 @@ public class LoginMenu {
|
|||||||
private boolean doRegister() throws IOException {
|
private boolean doRegister() throws IOException {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
printBanner();
|
printBanner();
|
||||||
System.out.println(ZAnsi.cyan(" [ Создание аккаунта ]"));
|
System.out.println(ZAnsi.cyan(" [ Create Account ]"));
|
||||||
System.out.println();
|
System.out.println();
|
||||||
System.out.println(ZAnsi.yellow(" Допустимые символы в имени: a-z, A-Z, 0-9, _"));
|
System.out.println(ZAnsi.yellow(" Allowed characters: a-z, A-Z, 0-9, _"));
|
||||||
System.out.println(ZAnsi.yellow(" Длина имени: 3-16 символов | Длина пароля: от 6 символов"));
|
System.out.println(ZAnsi.yellow(" Name length: 3-16 chars | Password length: 6+ chars"));
|
||||||
System.out.println();
|
System.out.println();
|
||||||
|
|
||||||
String username = Input.readLine(ZAnsi.white(" Имя пользователя: "));
|
String username = Input.readLine(ZAnsi.white(" Username: "));
|
||||||
if (username.isEmpty()) return false;
|
if (username.isEmpty()) return false;
|
||||||
|
|
||||||
String password = readPassword(" Пароль: ");
|
String password = readPassword(" Password: ");
|
||||||
if (password.isEmpty()) return false;
|
if (password.isEmpty()) return false;
|
||||||
|
|
||||||
String confirm = readPassword(" Повторите пароль: ");
|
String confirm = readPassword(" Confirm password: ");
|
||||||
if (!password.equals(confirm)) {
|
if (!password.equals(confirm)) {
|
||||||
System.out.println(ZAnsi.brightRed("\n Пароли не совпадают!"));
|
System.out.println(ZAnsi.brightRed("\n Passwords do not match!"));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println();
|
System.out.println();
|
||||||
System.out.print(ZAnsi.cyan(" Создаём аккаунт..."));
|
System.out.print(ZAnsi.cyan(" Creating account..."));
|
||||||
|
|
||||||
AuthResult result = AuthManager.register(username, password);
|
AuthResult result = AuthManager.register(username, password);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
System.out.println("\r" + ZAnsi.brightGreen(" Аккаунт создан! Добро пожаловать, " + AuthManager.getUsername() + "! "));
|
System.out.println("\r" + ZAnsi.brightGreen(" Account created! Welcome, " + AuthManager.getUsername() + "! "));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
System.out.println("\r" + ZAnsi.brightRed(" Ошибка: " + result.error + " "));
|
System.out.println("\r" + ZAnsi.brightRed(" Error: " + result.error + " "));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Читаем пароль — стараемся скрыть вывод через Console,
|
|
||||||
* если недоступно (IDE/терминал без TTY) — читаем обычным способом.
|
|
||||||
*/
|
|
||||||
private String readPassword(String prompt) throws IOException {
|
private String readPassword(String prompt) throws IOException {
|
||||||
org.jline.terminal.Terminal passTerminal = org.jline.terminal.TerminalBuilder.builder()
|
org.jline.terminal.Terminal passTerminal = org.jline.terminal.TerminalBuilder.builder()
|
||||||
.system(true)
|
.system(true)
|
||||||
@@ -165,27 +145,26 @@ public class LoginMenu {
|
|||||||
int key = passTerminal.reader().read();
|
int key = passTerminal.reader().read();
|
||||||
|
|
||||||
if (key == 27) {
|
if (key == 27) {
|
||||||
// Escape sequence — consume remaining bytes (arrow keys, etc.)
|
|
||||||
int next = passTerminal.reader().read();
|
int next = passTerminal.reader().read();
|
||||||
if (next == 91) { // '[' — arrow key sequence
|
if (next == 91) {
|
||||||
passTerminal.reader().read(); // consume 'A'/'B'/'C'/'D'
|
passTerminal.reader().read();
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key == 13 || key == 10) { // Enter
|
if (key == 13 || key == 10) {
|
||||||
passTerminal.writer().println();
|
passTerminal.writer().println();
|
||||||
break;
|
break;
|
||||||
} else if (key == 127 || key == 8) { // Backspace
|
} else if (key == 127 || key == 8) {
|
||||||
if (password.length() > 0) {
|
if (password.length() > 0) {
|
||||||
password.setLength(password.length() - 1);
|
password.setLength(password.length() - 1);
|
||||||
passTerminal.writer().print("\b \b");
|
passTerminal.writer().print("\b \b");
|
||||||
passTerminal.writer().flush();
|
passTerminal.writer().flush();
|
||||||
}
|
}
|
||||||
} else if (key == 3) { // Ctrl+C
|
} else if (key == 3) {
|
||||||
passTerminal.writer().println();
|
passTerminal.writer().println();
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
} else if (key >= 32 && key < 127) { // Printable characters
|
} else if (key >= 32 && key < 127) {
|
||||||
password.append((char) key);
|
password.append((char) key);
|
||||||
passTerminal.writer().print('*');
|
passTerminal.writer().print('*');
|
||||||
passTerminal.writer().flush();
|
passTerminal.writer().flush();
|
||||||
|
|||||||
+27
-27
@@ -18,17 +18,17 @@ public class ServerCheckMenu {
|
|||||||
public void show() throws IOException {
|
public void show() throws IOException {
|
||||||
while (true) {
|
while (true) {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
System.out.println(ZAnsi.header("Диагностика подключения"));
|
System.out.println(ZAnsi.header("Connection Diagnostics"));
|
||||||
|
|
||||||
List<String> options = List.of(
|
List<String> options = List.of(
|
||||||
"Проверить подключение к ZernMC серверу",
|
"Check ZernMC server connection",
|
||||||
"Проверить доступ к Mojang (Minecraft)",
|
"Check Mojang (Minecraft) access",
|
||||||
"Проверить доступ к Fabric Meta",
|
"Check Fabric Meta access",
|
||||||
"Проверить доступ к Forge Maven",
|
"Check Forge Maven access",
|
||||||
"Назад в главное меню"
|
"Back to main menu"
|
||||||
);
|
);
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu("Выберите проверку", options);
|
ArrowMenu menu = new ArrowMenu("Select check", options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
|
|
||||||
if (choice == -1 || choice == 4) {
|
if (choice == -1 || choice == 4) {
|
||||||
@@ -49,20 +49,20 @@ public class ServerCheckMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkZernServer() {
|
private void checkZernServer() {
|
||||||
System.out.println(ZAnsi.cyan("Проверка подключения к ZernMC серверу..."));
|
System.out.println(ZAnsi.cyan("Checking connection to ZernMC server..."));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String response = ZHttpClient.get("/health");
|
String response = ZHttpClient.get("/health");
|
||||||
System.out.println(ZAnsi.brightGreen("[OK] ZernMC сервер успешно подключён!"));
|
System.out.println(ZAnsi.brightGreen("[OK] ZernMC server connected successfully!"));
|
||||||
System.out.println(ZAnsi.white("Ответ сервера: ") + response);
|
System.out.println(ZAnsi.white("Server response: ") + response);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.brightRed("[FAIL] Не удалось подключиться к ZernMC серверу"));
|
System.out.println(ZAnsi.brightRed("[FAIL] Could not connect to ZernMC server"));
|
||||||
System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
|
System.out.println(ZAnsi.white("Error: ") + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkMojang() {
|
private void checkMojang() {
|
||||||
System.out.println(ZAnsi.cyan("Проверка доступа к Mojang..."));
|
System.out.println(ZAnsi.cyan("Checking Mojang access..."));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HttpClient client = HttpClient.newBuilder()
|
HttpClient client = HttpClient.newBuilder()
|
||||||
@@ -77,18 +77,18 @@ public class ServerCheckMenu {
|
|||||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
if (response.statusCode() == 200) {
|
if (response.statusCode() == 200) {
|
||||||
System.out.println(ZAnsi.brightGreen("[OK] Mojang доступен"));
|
System.out.println(ZAnsi.brightGreen("[OK] Mojang is accessible"));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed("[FAIL] Mojang вернул код " + response.statusCode()));
|
System.out.println(ZAnsi.brightRed("[FAIL] Mojang returned code " + response.statusCode()));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.brightRed("[FAIL] Нет доступа к Mojang"));
|
System.out.println(ZAnsi.brightRed("[FAIL] Cannot access Mojang"));
|
||||||
System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
|
System.out.println(ZAnsi.white("Error: ") + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkFabric() {
|
private void checkFabric() {
|
||||||
System.out.println(ZAnsi.cyan("Проверка доступа к Fabric Meta..."));
|
System.out.println(ZAnsi.cyan("Checking Fabric Meta access..."));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HttpClient client = HttpClient.newBuilder()
|
HttpClient client = HttpClient.newBuilder()
|
||||||
@@ -103,18 +103,18 @@ public class ServerCheckMenu {
|
|||||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
if (response.statusCode() == 200) {
|
if (response.statusCode() == 200) {
|
||||||
System.out.println(ZAnsi.brightGreen("[OK] Fabric Meta доступен"));
|
System.out.println(ZAnsi.brightGreen("[OK] Fabric Meta is accessible"));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed("[FAIL] Fabric Meta вернул код " + response.statusCode()));
|
System.out.println(ZAnsi.brightRed("[FAIL] Fabric Meta returned code " + response.statusCode()));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.brightRed("[FAIL] Нет доступа к Fabric Meta"));
|
System.out.println(ZAnsi.brightRed("[FAIL] Cannot access Fabric Meta"));
|
||||||
System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
|
System.out.println(ZAnsi.white("Error: ") + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkForge() {
|
private void checkForge() {
|
||||||
System.out.println(ZAnsi.cyan("Проверка доступа к Forge Maven..."));
|
System.out.println(ZAnsi.cyan("Checking Forge Maven access..."));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HttpClient client = HttpClient.newBuilder()
|
HttpClient client = HttpClient.newBuilder()
|
||||||
@@ -129,13 +129,13 @@ public class ServerCheckMenu {
|
|||||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
if (response.statusCode() == 200) {
|
if (response.statusCode() == 200) {
|
||||||
System.out.println(ZAnsi.brightGreen("[OK] Forge Maven доступен"));
|
System.out.println(ZAnsi.brightGreen("[OK] Forge Maven is accessible"));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed("[FAIL] Forge Maven вернул код " + response.statusCode()));
|
System.out.println(ZAnsi.brightRed("[FAIL] Forge Maven returned code " + response.statusCode()));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.brightRed("[FAIL] Нет доступа к Forge Maven"));
|
System.out.println(ZAnsi.brightRed("[FAIL] Cannot access Forge Maven"));
|
||||||
System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
|
System.out.println(ZAnsi.white("Error: ") + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,13 +13,13 @@ public class SettingsMenu {
|
|||||||
|
|
||||||
public void show() throws IOException {
|
public void show() throws IOException {
|
||||||
List<String> options = List.of(
|
List<String> options = List.of(
|
||||||
"Настроить путь к Java",
|
"Configure Java path",
|
||||||
"Настроить выделенную память (RAM)",
|
"Configure allocated RAM",
|
||||||
"Дополнительные JVM параметры",
|
"Additional JVM parameters",
|
||||||
"Назад в главное меню"
|
"Back to main menu"
|
||||||
);
|
);
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu("Настройки лаунчера", options);
|
ArrowMenu menu = new ArrowMenu("Launcher Settings", options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
|
|
||||||
if (choice == -1 || choice == 3) return;
|
if (choice == -1 || choice == 3) return;
|
||||||
@@ -36,33 +36,33 @@ public class SettingsMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void configureJava() {
|
private void configureJava() {
|
||||||
System.out.println(ZAnsi.cyan("Путь к Java:"));
|
System.out.println(ZAnsi.cyan("Java path:"));
|
||||||
System.out.println(" " + Config.getJreDir().toAbsolutePath());
|
System.out.println(" " + Config.getJreDir().toAbsolutePath());
|
||||||
System.out.println(ZAnsi.white("\nJava будет искаться автоматически в папке ~/.zernmc/jre/"));
|
System.out.println(ZAnsi.white("\nJava will be searched automatically in ~/.zernmc/jre/"));
|
||||||
System.out.println("Если нужно — положите туда свою версию Java.");
|
System.out.println("If needed, place your own Java version there.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureRam() {
|
private void configureRam() {
|
||||||
System.out.println(ZAnsi.cyan("Настройка выделенной памяти"));
|
System.out.println(ZAnsi.cyan("RAM Allocation"));
|
||||||
System.out.println(Config.getRamInfo());
|
System.out.println(Config.getRamInfo());
|
||||||
|
|
||||||
int newRam = Input.readInt(
|
int newRam = Input.readInt(
|
||||||
ZAnsi.white("\nВведите новое значение RAM в MB (или 0 для отмены): "),
|
ZAnsi.white("\nEnter new RAM value in MB (or 0 to cancel): "),
|
||||||
0, 32768
|
0, 32768
|
||||||
);
|
);
|
||||||
|
|
||||||
if (newRam == 0) {
|
if (newRam == 0) {
|
||||||
System.out.println(ZAnsi.yellow("Настройка отменена."));
|
System.out.println(ZAnsi.yellow("Setting cancelled."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Config.setMaxMemory(newRam);
|
Config.setMaxMemory(newRam);
|
||||||
System.out.println(ZAnsi.brightGreen("Выделенная память изменена на " + newRam + " MB"));
|
System.out.println(ZAnsi.brightGreen("Allocated RAM changed to " + newRam + " MB"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureJvmArgs() {
|
private void configureJvmArgs() {
|
||||||
System.out.println(ZAnsi.yellow("Дополнительные JVM параметры"));
|
System.out.println(ZAnsi.yellow("Additional JVM parameters"));
|
||||||
System.out.println("Пока в разработке.");
|
System.out.println("Currently in development.");
|
||||||
System.out.println("В будущем здесь будет список предустановленных оптимизаций.");
|
System.out.println("A list of preset optimizations will be available in the future.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,12 +18,12 @@ public class UpdateMenu {
|
|||||||
|
|
||||||
public void show() throws IOException {
|
public void show() throws IOException {
|
||||||
List<String> options = List.of(
|
List<String> options = List.of(
|
||||||
"Проверить обновления сборки (модпака)",
|
"Check pack updates",
|
||||||
"Проверить обновления лаунчера",
|
"Check launcher updates",
|
||||||
"Назад в главное меню"
|
"Back to main menu"
|
||||||
);
|
);
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu("Проверка обновлений", options);
|
ArrowMenu menu = new ArrowMenu("Update Check", options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
|
|
||||||
if (choice == -1 || choice == 2) return;
|
if (choice == -1 || choice == 2) return;
|
||||||
@@ -34,7 +34,7 @@ public class UpdateMenu {
|
|||||||
try {
|
try {
|
||||||
checkPackUpdates();
|
checkPackUpdates();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.brightRed("Ошибка: " + e.getMessage()));
|
System.out.println(ZAnsi.brightRed("Error: " + e.getMessage()));
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ public class UpdateMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkPackUpdates() throws Exception {
|
private void checkPackUpdates() throws Exception {
|
||||||
System.out.println(ZAnsi.cyan("Проверка обновлений сборок..."));
|
System.out.println(ZAnsi.cyan("Checking pack updates..."));
|
||||||
|
|
||||||
List<Instance> instances = InstanceManager.getAllInstances();
|
List<Instance> instances = InstanceManager.getAllInstances();
|
||||||
List<Instance> serverInstances = instances.stream()
|
List<Instance> serverInstances = instances.stream()
|
||||||
@@ -52,12 +52,12 @@ public class UpdateMenu {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (serverInstances.isEmpty()) {
|
if (serverInstances.isEmpty()) {
|
||||||
System.out.println(ZAnsi.yellow("Нет сборок, установленных с сервера."));
|
System.out.println(ZAnsi.yellow("No server-installed packs found."));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("\nПроверка обновлений для серверных сборок:\n"));
|
System.out.println(ZAnsi.cyan("\nChecking updates for server packs:\n"));
|
||||||
|
|
||||||
boolean hasUpdates = false;
|
boolean hasUpdates = false;
|
||||||
List<Instance> updatableInstances = new ArrayList<>();
|
List<Instance> updatableInstances = new ArrayList<>();
|
||||||
@@ -68,42 +68,41 @@ public class UpdateMenu {
|
|||||||
try {
|
try {
|
||||||
boolean hasUpdate = downloader.checkForUpdates(instance.getServerPackName());
|
boolean hasUpdate = downloader.checkForUpdates(instance.getServerPackName());
|
||||||
if (hasUpdate) {
|
if (hasUpdate) {
|
||||||
System.out.println(ZAnsi.yellow(instance.getName() + " - Есть обновление!"));
|
System.out.println(ZAnsi.yellow(instance.getName() + " - Update available!"));
|
||||||
updatableInstances.add(instance);
|
updatableInstances.add(instance);
|
||||||
hasUpdates = true;
|
hasUpdates = true;
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.green(instance.getName() + " - Актуальна"));
|
System.out.println(ZAnsi.green(instance.getName() + " - Up to date"));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.red(instance.getName() + " - Ошибка проверки: " + e.getMessage()));
|
System.out.println(ZAnsi.red(instance.getName() + " - Check error: " + e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasUpdates) {
|
if (!hasUpdates) {
|
||||||
System.out.println(ZAnsi.green("\nВсе сборки актуальны!"));
|
System.out.println(ZAnsi.green("\nAll packs are up to date!"));
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Предлагаем обновить каждую сборку отдельно
|
|
||||||
for (Instance instance : updatableInstances) {
|
for (Instance instance : updatableInstances) {
|
||||||
System.out.println(ZAnsi.brightYellow("\nОбновить сборку '" + instance.getName() + "'?"));
|
System.out.println(ZAnsi.brightYellow("\nUpdate pack '" + instance.getName() + "'?"));
|
||||||
if (Input.confirm("Обновить")) {
|
if (Input.confirm("Update")) {
|
||||||
System.out.println(ZAnsi.cyan("Обновление " + instance.getName() + "..."));
|
System.out.println(ZAnsi.cyan("Updating " + instance.getName() + "..."));
|
||||||
PackDownloader downloader = new PackDownloader(instance);
|
PackDownloader downloader = new PackDownloader(instance);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
boolean success = downloader.updatePack(instance.getServerPackName());
|
boolean success = downloader.updatePack(instance.getServerPackName());
|
||||||
if (success) {
|
if (success) {
|
||||||
System.out.println(ZAnsi.brightGreen(instance.getName() + " обновлен"));
|
System.out.println(ZAnsi.brightGreen(instance.getName() + " updated"));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed(instance.getName() + " не удалось обновить"));
|
System.out.println(ZAnsi.brightRed(instance.getName() + " update failed"));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.brightRed(instance.getName() + ": " + e.getMessage()));
|
System.out.println(ZAnsi.brightRed(instance.getName() + ": " + e.getMessage()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.yellow(" Пропущено: " + instance.getName()));
|
System.out.println(ZAnsi.yellow(" Skipped: " + instance.getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,28 +110,27 @@ public class UpdateMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkLauncherUpdates() {
|
private void checkLauncherUpdates() {
|
||||||
System.out.println(ZAnsi.cyan("Проверка обновлений лаунчера..."));
|
System.out.println(ZAnsi.cyan("Checking launcher updates..."));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String json = ZHttpClient.getLauncherVersionInfo();
|
String json = ZHttpClient.getLauncherVersionInfo();
|
||||||
String serverVersion = extractVersion(json);
|
String serverVersion = extractVersion(json);
|
||||||
String currentVersion = me.sashegdev.zernmc.launcher.utils.Version.getCurrentVersion();
|
String currentVersion = me.sashegdev.zernmc.launcher.utils.Version.getCurrentVersion();
|
||||||
|
|
||||||
System.out.println(ZAnsi.white("Текущая версия: ") + currentVersion);
|
System.out.println(ZAnsi.white("Current version: ") + currentVersion);
|
||||||
System.out.println(ZAnsi.white("Версия на сервере: ") + serverVersion);
|
System.out.println(ZAnsi.white("Server version: ") + serverVersion);
|
||||||
|
|
||||||
if (me.sashegdev.zernmc.launcher.utils.Version.isNewer(currentVersion, serverVersion)) {
|
if (me.sashegdev.zernmc.launcher.utils.Version.isNewer(currentVersion, serverVersion)) {
|
||||||
System.out.println(ZAnsi.brightYellow("\nДоступна новая версия!"));
|
System.out.println(ZAnsi.brightYellow("\nNew version available!"));
|
||||||
if (Input.confirm("Обновить лаунчер?")) {
|
if (Input.confirm("Update launcher?")) {
|
||||||
// Обновление будет при следующем запуске
|
System.out.println(ZAnsi.green("Launcher will be updated on next restart."));
|
||||||
System.out.println(ZAnsi.green("Лаунчер будет обновлен при следующем запуске."));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightGreen("Лаунчер актуален."));
|
System.out.println(ZAnsi.brightGreen("Launcher is up to date."));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.yellow("Не удалось проверить обновления лаунчера."));
|
System.out.println(ZAnsi.yellow("Could not check launcher updates."));
|
||||||
System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
|
System.out.println(ZAnsi.white("Error: ") + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsoleUtils.pause();
|
ConsoleUtils.pause();
|
||||||
|
|||||||
+21
-21
@@ -56,7 +56,7 @@ public class MinecraftLib {
|
|||||||
boolean success = installer.install(minecraftVersion, loaderVersion);
|
boolean success = installer.install(minecraftVersion, loaderVersion);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
// Сохраняем информацию в Instance
|
// Save info to Instance
|
||||||
instance.setMinecraftVersion(minecraftVersion);
|
instance.setMinecraftVersion(minecraftVersion);
|
||||||
instance.setLoaderType("fabric");
|
instance.setLoaderType("fabric");
|
||||||
instance.setLoaderVersion(loaderVersion);
|
instance.setLoaderVersion(loaderVersion);
|
||||||
@@ -65,61 +65,61 @@ public class MinecraftLib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Полная установка сборки (vanilla + loader + моды)
|
* Full pack install (vanilla + loader + mods)
|
||||||
* Пока заглушка — будем расширять
|
* Stub - will be expanded
|
||||||
*/
|
*/
|
||||||
public boolean installPack(String packName, String minecraftVersion, String loaderType, String loaderVersion) throws Exception {
|
public boolean installPack(String packName, String minecraftVersion, String loaderType, String loaderVersion) throws Exception {
|
||||||
System.out.println(ZAnsi.cyan("Начинается полная установка сборки: " + packName));
|
System.out.println(ZAnsi.cyan("Starting full pack install: " + packName));
|
||||||
|
|
||||||
// 1. Устанавливаем Minecraft
|
// 1. Install Minecraft
|
||||||
boolean mcInstalled = installMinecraft(minecraftVersion);
|
boolean mcInstalled = installMinecraft(minecraftVersion);
|
||||||
if (!mcInstalled) {
|
if (!mcInstalled) {
|
||||||
System.out.println(ZAnsi.brightRed("Не удалось установить Minecraft " + minecraftVersion));
|
System.out.println(ZAnsi.brightRed("Failed to install Minecraft " + minecraftVersion));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Устанавливаем лоадер
|
// 2. Install loader
|
||||||
if ("fabric".equalsIgnoreCase(loaderType)) {
|
if ("fabric".equalsIgnoreCase(loaderType)) {
|
||||||
boolean fabricInstalled = installFabric(minecraftVersion, loaderVersion);
|
boolean fabricInstalled = installFabric(minecraftVersion, loaderVersion);
|
||||||
if (!fabricInstalled) {
|
if (!fabricInstalled) {
|
||||||
System.out.println(ZAnsi.brightRed("Не удалось установить Fabric"));
|
System.out.println(ZAnsi.brightRed("Failed to install Fabric"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if ("forge".equalsIgnoreCase(loaderType)) {
|
} else if ("forge".equalsIgnoreCase(loaderType)) {
|
||||||
boolean forgeInstalled = installForge(minecraftVersion, loaderVersion);
|
boolean forgeInstalled = installForge(minecraftVersion, loaderVersion);
|
||||||
if (!forgeInstalled) {
|
if (!forgeInstalled) {
|
||||||
System.out.println(ZAnsi.brightRed("Не удалось установить Forge"));
|
System.out.println(ZAnsi.brightRed("Failed to install Forge"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if ("neoforge".equalsIgnoreCase(loaderType)) {
|
} else if ("neoforge".equalsIgnoreCase(loaderType)) {
|
||||||
boolean neoforgeInstalled = installNeoForge(minecraftVersion, loaderVersion);
|
boolean neoforgeInstalled = installNeoForge(minecraftVersion, loaderVersion);
|
||||||
if (!neoforgeInstalled) {
|
if (!neoforgeInstalled) {
|
||||||
System.out.println(ZAnsi.brightRed("Не удалось установить NeoForge"));
|
System.out.println(ZAnsi.brightRed("Failed to install NeoForge"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. В будущем здесь будет diff и скачивание модов
|
// 3. In the future: diff and mod download
|
||||||
|
|
||||||
System.out.println(ZAnsi.brightGreen("Базовая установка сборки завершена!"));
|
System.out.println(ZAnsi.brightGreen("Basic pack install complete!"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Запуск
|
//Launch
|
||||||
public void launch(LaunchOptions options) throws Exception {
|
public void launch(LaunchOptions options) throws Exception {
|
||||||
System.out.println(ZAnsi.brightGreen("Запуск сборки: " + instance.getName()));
|
System.out.println(ZAnsi.brightGreen("Launching pack: " + instance.getName()));
|
||||||
cleanupOldLoaders();
|
cleanupOldLoaders();
|
||||||
|
|
||||||
LaunchCommandBuilder builder = new LaunchCommandBuilder(instance);
|
LaunchCommandBuilder builder = new LaunchCommandBuilder(instance);
|
||||||
List<String> command = builder.build(options);
|
List<String> command = builder.build(options);
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("Команда запуска (" + command.size() + " аргументов):"));
|
System.out.println(ZAnsi.cyan("Launch command (" + command.size() + " args):"));
|
||||||
command.forEach(arg -> System.out.println(" " + arg));
|
command.forEach(arg -> System.out.println(" " + arg));
|
||||||
|
|
||||||
ProcessBuilder pb = new ProcessBuilder(command);
|
ProcessBuilder pb = new ProcessBuilder(command);
|
||||||
pb.directory(instance.getPath().toFile());
|
pb.directory(instance.getPath().toFile());
|
||||||
|
|
||||||
System.out.println(ZAnsi.brightGreen("\nЗапускаем Minecraft...\n"));
|
System.out.println(ZAnsi.brightGreen("\nStarting Minecraft...\n"));
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
|
|
||||||
Process process = pb.start();
|
Process process = pb.start();
|
||||||
@@ -132,7 +132,7 @@ public class MinecraftLib {
|
|||||||
JFXLauncher.appendGameLog(line);
|
JFXLauncher.appendGameLog(line);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
JFXLauncher.appendGameLog("[Ошибка чтения вывода: " + e.getMessage() + "]");
|
JFXLauncher.appendGameLog("[Error reading output: " + e.getMessage() + "]");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
outThread.setDaemon(true);
|
outThread.setDaemon(true);
|
||||||
@@ -146,7 +146,7 @@ public class MinecraftLib {
|
|||||||
JFXLauncher.appendGameLog("[ERR] " + line);
|
JFXLauncher.appendGameLog("[ERR] " + line);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
JFXLauncher.appendGameLog("[Ошибка чтения ошибок: " + e.getMessage() + "]");
|
JFXLauncher.appendGameLog("[Error reading stderr: " + e.getMessage() + "]");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
errThread.setDaemon(true);
|
errThread.setDaemon(true);
|
||||||
@@ -156,7 +156,7 @@ public class MinecraftLib {
|
|||||||
outThread.join(1000);
|
outThread.join(1000);
|
||||||
errThread.join(1000);
|
errThread.join(1000);
|
||||||
|
|
||||||
System.out.println(ZAnsi.yellow("\nMinecraft завершился с кодом: " + exitCode));
|
System.out.println(ZAnsi.yellow("\nMinecraft exited with code: " + exitCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void safeDeleteDirectory(Path dir) {
|
private void safeDeleteDirectory(Path dir) {
|
||||||
@@ -202,9 +202,9 @@ public class MinecraftLib {
|
|||||||
|
|
||||||
if (currentLoaderVer == null) return;
|
if (currentLoaderVer == null) return;
|
||||||
|
|
||||||
System.out.println(ZAnsi.yellow("Выполняем очистку старых версий лоадера..."));
|
System.out.println(ZAnsi.yellow("Cleaning old loader versions..."));
|
||||||
|
|
||||||
// Удаляем все старые fabric-loader / forge
|
// Delete all old fabric-loader / forge
|
||||||
Path libraries = instance.getPath().resolve("libraries");
|
Path libraries = instance.getPath().resolve("libraries");
|
||||||
|
|
||||||
if ("fabric".equals(loaderType)) {
|
if ("fabric".equals(loaderType)) {
|
||||||
|
|||||||
+63
-63
@@ -36,18 +36,18 @@ public class PackDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получить список доступных паков с сервера
|
* Get list of available packs from server
|
||||||
*/
|
*/
|
||||||
public List<ServerPack> getAvailablePacks() throws Exception {
|
public List<ServerPack> getAvailablePacks() throws Exception {
|
||||||
String accessToken = AuthManager.getAccessToken();
|
String accessToken = AuthManager.getAccessToken();
|
||||||
if (accessToken == null) {
|
if (accessToken == null) {
|
||||||
throw new IOException("Не авторизован. Требуется проходка для просмотра сборок.");
|
throw new IOException("Not authenticated. Active pass required to view packs.");
|
||||||
}
|
}
|
||||||
if (!AuthManager.canViewPacks()) {
|
if (!AuthManager.canViewPacks()) {
|
||||||
throw new IOException("Для просмотра сборок требуется активная проходка");
|
throw new IOException("Active pass required to view packs");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Используем HttpURLConnection для GET с авторизацией
|
// Use HttpURLConnection for GET with auth
|
||||||
java.net.HttpURLConnection connection = null;
|
java.net.HttpURLConnection connection = null;
|
||||||
try {
|
try {
|
||||||
java.net.URL url = new java.net.URL(ZHttpClient.getBaseUrl() + "/packs");
|
java.net.URL url = new java.net.URL(ZHttpClient.getBaseUrl() + "/packs");
|
||||||
@@ -61,7 +61,7 @@ public class PackDownloader {
|
|||||||
int responseCode = connection.getResponseCode();
|
int responseCode = connection.getResponseCode();
|
||||||
|
|
||||||
if (responseCode == 403) {
|
if (responseCode == 403) {
|
||||||
throw new IOException("Для просмотра сборок требуется активная проходка");
|
throw new IOException("Active pass required to view packs");
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder response = new StringBuilder();
|
StringBuilder response = new StringBuilder();
|
||||||
@@ -118,7 +118,7 @@ public class PackDownloader {
|
|||||||
result.add(new ServerPack(name, version, minecraftVersion, loaderType,
|
result.add(new ServerPack(name, version, minecraftVersion, loaderType,
|
||||||
loaderVersion, updatedAt, filesCount));
|
loaderVersion, updatedAt, filesCount));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("Ошибка парсинга пака: " + e.getMessage());
|
System.err.println("Error parsing pack: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ public class PackDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получить манифест пака
|
* Get pack manifest
|
||||||
*/
|
*/
|
||||||
public PackManifest getPackManifest(String packName) throws Exception {
|
public PackManifest getPackManifest(String packName) throws Exception {
|
||||||
String response = ZHttpClient.get("/pack/" + packName);
|
String response = ZHttpClient.get("/pack/" + packName);
|
||||||
@@ -134,18 +134,18 @@ public class PackDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Установить или обновить сборку с сервера
|
* Install or update a pack from the server
|
||||||
*/
|
*/
|
||||||
public boolean installOrUpdatePack(String packName, ServerPack serverPack) throws Exception {
|
public boolean installOrUpdatePack(String packName, ServerPack serverPack) throws Exception {
|
||||||
System.out.println(ZAnsi.cyan("Установка сборки " + packName + " с сервера..."));
|
System.out.println(ZAnsi.cyan("Installing pack " + packName + " from server..."));
|
||||||
|
|
||||||
// 1. Получаем манифест
|
// 1. Get manifest
|
||||||
PackManifest manifest = getPackManifest(packName);
|
PackManifest manifest = getPackManifest(packName);
|
||||||
|
|
||||||
// 2. Сначала устанавливаем Minecraft + Loader через MinecraftLib
|
// 2. First install Minecraft + Loader via MinecraftLib
|
||||||
MinecraftLib lib = new MinecraftLib(instance);
|
MinecraftLib lib = new MinecraftLib(instance);
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("Установка Minecraft " + manifest.getMinecraftVersion() + "..."));
|
System.out.println(ZAnsi.cyan("Installing Minecraft " + manifest.getMinecraftVersion() + "..."));
|
||||||
|
|
||||||
boolean needsMinecraftInstall = instance.getMinecraftVersion() == null ||
|
boolean needsMinecraftInstall = instance.getMinecraftVersion() == null ||
|
||||||
!instance.getMinecraftVersion().equals(manifest.getMinecraftVersion());
|
!instance.getMinecraftVersion().equals(manifest.getMinecraftVersion());
|
||||||
@@ -154,40 +154,40 @@ public class PackDownloader {
|
|||||||
if ("fabric".equalsIgnoreCase(manifest.getLoaderType())) {
|
if ("fabric".equalsIgnoreCase(manifest.getLoaderType())) {
|
||||||
boolean success = lib.installFabric(manifest.getMinecraftVersion(), manifest.getLoaderVersion());
|
boolean success = lib.installFabric(manifest.getMinecraftVersion(), manifest.getLoaderVersion());
|
||||||
if (!success) {
|
if (!success) {
|
||||||
System.err.println(ZAnsi.brightRed("Не удалось установить Fabric"));
|
System.err.println(ZAnsi.brightRed("Failed to install Fabric"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if ("neoforge".equalsIgnoreCase(manifest.getLoaderType())) {
|
} else if ("neoforge".equalsIgnoreCase(manifest.getLoaderType())) {
|
||||||
boolean success = lib.installNeoForge(manifest.getMinecraftVersion(), manifest.getLoaderVersion());
|
boolean success = lib.installNeoForge(manifest.getMinecraftVersion(), manifest.getLoaderVersion());
|
||||||
if (!success) {
|
if (!success) {
|
||||||
System.err.println(ZAnsi.brightRed("Не удалось установить NeoForge"));
|
System.err.println(ZAnsi.brightRed("Failed to install NeoForge"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if ("forge".equalsIgnoreCase(manifest.getLoaderType())) {
|
} else if ("forge".equalsIgnoreCase(manifest.getLoaderType())) {
|
||||||
boolean success = lib.installForge(manifest.getMinecraftVersion(), manifest.getLoaderVersion());
|
boolean success = lib.installForge(manifest.getMinecraftVersion(), manifest.getLoaderVersion());
|
||||||
if (!success) {
|
if (!success) {
|
||||||
System.err.println(ZAnsi.brightRed("Не удалось установить Forge"));
|
System.err.println(ZAnsi.brightRed("Failed to install Forge"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
boolean success = lib.installMinecraft(manifest.getMinecraftVersion());
|
boolean success = lib.installMinecraft(manifest.getMinecraftVersion());
|
||||||
if (!success) {
|
if (!success) {
|
||||||
System.err.println(ZAnsi.brightRed("Не удалось установить Vanilla Minecraft"));
|
System.err.println(ZAnsi.brightRed("Failed to install Vanilla Minecraft"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.green("Minecraft уже установлен, пропускаем..."));
|
System.out.println(ZAnsi.green("Minecraft already installed, skipping..."));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Сканируем локальные файлы ТОЛЬКО если есть файлы для скачивания
|
// 3. Scan local files only if there are files to download
|
||||||
Map<String, String> localFiles = scanLocalFiles();
|
Map<String, String> localFiles = scanLocalFiles();
|
||||||
|
|
||||||
// Если в сборке нет файлов (только vanilla/loader), пропускаем diff
|
// If pack has no files (vanilla/loader only), skip diff
|
||||||
if (manifest.files == null || manifest.files.isEmpty()) {
|
if (manifest.files == null || manifest.files.isEmpty()) {
|
||||||
System.out.println(ZAnsi.green("Сборка не содержит дополнительных файлов"));
|
System.out.println(ZAnsi.green("Pack contains no additional files"));
|
||||||
|
|
||||||
// Обновляем метаданные инстанса
|
// Update instance metadata
|
||||||
instance.setServerPack(true);
|
instance.setServerPack(true);
|
||||||
instance.setServerPackName(packName);
|
instance.setServerPackName(packName);
|
||||||
instance.setServerVersion(manifest.getVersion());
|
instance.setServerVersion(manifest.getVersion());
|
||||||
@@ -196,19 +196,19 @@ public class PackDownloader {
|
|||||||
instance.setLoaderVersion(manifest.getLoaderVersion());
|
instance.setLoaderVersion(manifest.getLoaderVersion());
|
||||||
instance.setAssetIndex(manifest.getAssetIndex());
|
instance.setAssetIndex(manifest.getAssetIndex());
|
||||||
|
|
||||||
System.out.println(ZAnsi.brightGreen("Сборка успешно установлена!"));
|
System.out.println(ZAnsi.brightGreen("Pack installed successfully!"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Отправляем diff запрос
|
// 4. Send diff request
|
||||||
System.out.println(ZAnsi.cyan("Проверка файлов сборки..."));
|
System.out.println(ZAnsi.cyan("Checking pack files..."));
|
||||||
DiffResponse diff = getDiff(packName, localFiles);
|
DiffResponse diff = getDiff(packName, localFiles);
|
||||||
|
|
||||||
// 5. Применяем изменения
|
// 5. Apply changes
|
||||||
boolean success = applyDiff(diff, packName);
|
boolean success = applyDiff(diff, packName);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
// 6. Обновляем метаданные инстанса
|
// 6. Update instance metadata
|
||||||
instance.setServerPack(true);
|
instance.setServerPack(true);
|
||||||
instance.setServerPackName(packName);
|
instance.setServerPackName(packName);
|
||||||
instance.setServerVersion(manifest.getVersion());
|
instance.setServerVersion(manifest.getVersion());
|
||||||
@@ -217,14 +217,14 @@ public class PackDownloader {
|
|||||||
instance.setLoaderVersion(manifest.getLoaderVersion());
|
instance.setLoaderVersion(manifest.getLoaderVersion());
|
||||||
instance.setAssetIndex(manifest.getAssetIndex());
|
instance.setAssetIndex(manifest.getAssetIndex());
|
||||||
|
|
||||||
System.out.println(ZAnsi.brightGreen("Сборка успешно установлена!"));
|
System.out.println(ZAnsi.brightGreen("Pack installed successfully!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Проверить наличие обновлений для серверной сборки
|
* Check for server pack updates
|
||||||
*/
|
*/
|
||||||
public boolean checkForUpdates(String packName) throws Exception {
|
public boolean checkForUpdates(String packName) throws Exception {
|
||||||
if (!instance.isServerPack()) return false;
|
if (!instance.isServerPack()) return false;
|
||||||
@@ -237,40 +237,40 @@ public class PackDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обновить существующую серверную сборку
|
* Update an existing server pack
|
||||||
*/
|
*/
|
||||||
public boolean updatePack(String packName) throws Exception {
|
public boolean updatePack(String packName) throws Exception {
|
||||||
System.out.println(ZAnsi.cyan("Проверка обновлений для " + instance.getName() + "..."));
|
System.out.println(ZAnsi.cyan("Checking updates for " + instance.getName() + "..."));
|
||||||
|
|
||||||
PackManifest manifest = getPackManifest(packName);
|
PackManifest manifest = getPackManifest(packName);
|
||||||
int serverVersion = manifest.getVersion();
|
int serverVersion = manifest.getVersion();
|
||||||
|
|
||||||
if (serverVersion <= instance.getServerVersion()) {
|
if (serverVersion <= instance.getServerVersion()) {
|
||||||
System.out.println(ZAnsi.green("Сборка уже актуальна (v" + instance.getServerVersion() + ")"));
|
System.out.println(ZAnsi.green("Pack is already up to date (v" + instance.getServerVersion() + ")"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println(ZAnsi.yellow("Доступно обновление: v" + instance.getServerVersion() + " → v" + serverVersion));
|
System.out.println(ZAnsi.yellow("Update available: v" + instance.getServerVersion() + " → v" + serverVersion));
|
||||||
|
|
||||||
// Сканируем локальные файлы
|
// Scan local files
|
||||||
Map<String, String> localFiles = scanLocalFiles();
|
Map<String, String> localFiles = scanLocalFiles();
|
||||||
|
|
||||||
// Получаем diff
|
// Get diff
|
||||||
DiffResponse diff = getDiff(packName, localFiles);
|
DiffResponse diff = getDiff(packName, localFiles);
|
||||||
|
|
||||||
// Применяем изменения
|
// Apply changes
|
||||||
boolean success = applyDiff(diff, packName);
|
boolean success = applyDiff(diff, packName);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
instance.setServerVersion(serverVersion);
|
instance.setServerVersion(serverVersion);
|
||||||
System.out.println(ZAnsi.brightGreen("Сборка обновлена до v" + serverVersion));
|
System.out.println(ZAnsi.brightGreen("Pack updated to v" + serverVersion));
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Сканирование локальных файлов и вычисление хешей
|
* Scan local files and compute hashes
|
||||||
*/
|
*/
|
||||||
private Map<String, String> scanLocalFiles() throws IOException {
|
private Map<String, String> scanLocalFiles() throws IOException {
|
||||||
Map<String, String> files = new HashMap<>();
|
Map<String, String> files = new HashMap<>();
|
||||||
@@ -312,23 +312,23 @@ public class PackDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Отправить diff запрос на сервер
|
* Send diff request to server
|
||||||
*/
|
*/
|
||||||
private DiffResponse getDiff(String packName, Map<String, String> localFiles) throws Exception {
|
private DiffResponse getDiff(String packName, Map<String, String> localFiles) throws Exception {
|
||||||
String json = gson.toJson(localFiles);
|
String json = gson.toJson(localFiles);
|
||||||
|
|
||||||
// Получаем токен авторизации
|
// Get auth token
|
||||||
String accessToken = AuthManager.getAccessToken();
|
String accessToken = AuthManager.getAccessToken();
|
||||||
if (accessToken == null) {
|
if (accessToken == null) {
|
||||||
throw new IOException("Не авторизован. Требуется проходка для скачивания сборок.");
|
throw new IOException("Not authenticated. Active pass required to download packs.");
|
||||||
}
|
}
|
||||||
if (!AuthManager.canDownloadPacks()) {
|
if (!AuthManager.canDownloadPacks()) {
|
||||||
throw new IOException("Для скачивания сборок требуется активная проходка");
|
throw new IOException("Active pass required to download packs");
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = ZHttpClient.getBaseUrl() + "/pack/" + packName + "/diff";
|
String url = ZHttpClient.getBaseUrl() + "/pack/" + packName + "/diff";
|
||||||
|
|
||||||
// Используем HttpURLConnection для полного контроля
|
// Use HttpURLConnection for full control
|
||||||
java.net.HttpURLConnection connection = null;
|
java.net.HttpURLConnection connection = null;
|
||||||
try {
|
try {
|
||||||
java.net.URL urlObj = new java.net.URL(url);
|
java.net.URL urlObj = new java.net.URL(url);
|
||||||
@@ -342,7 +342,7 @@ public class PackDownloader {
|
|||||||
connection.setConnectTimeout(30000);
|
connection.setConnectTimeout(30000);
|
||||||
connection.setReadTimeout(30000);
|
connection.setReadTimeout(30000);
|
||||||
|
|
||||||
// Отправляем JSON
|
// Send JSON
|
||||||
try (java.io.OutputStream os = connection.getOutputStream()) {
|
try (java.io.OutputStream os = connection.getOutputStream()) {
|
||||||
byte[] input = json.getBytes("UTF-8");
|
byte[] input = json.getBytes("UTF-8");
|
||||||
os.write(input, 0, input.length);
|
os.write(input, 0, input.length);
|
||||||
@@ -351,7 +351,7 @@ public class PackDownloader {
|
|||||||
|
|
||||||
int responseCode = connection.getResponseCode();
|
int responseCode = connection.getResponseCode();
|
||||||
|
|
||||||
// Читаем ответ
|
// Read response
|
||||||
StringBuilder response = new StringBuilder();
|
StringBuilder response = new StringBuilder();
|
||||||
try (java.io.InputStream is = responseCode < 400 ? connection.getInputStream() : connection.getErrorStream();
|
try (java.io.InputStream is = responseCode < 400 ? connection.getInputStream() : connection.getErrorStream();
|
||||||
java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(is, "UTF-8"))) {
|
java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(is, "UTF-8"))) {
|
||||||
@@ -364,7 +364,7 @@ public class PackDownloader {
|
|||||||
String responseBody = response.toString();
|
String responseBody = response.toString();
|
||||||
|
|
||||||
if (responseCode == 403) {
|
if (responseCode == 403) {
|
||||||
throw new IOException("Для скачивания сборок требуется активная проходка. Обратитесь к администратору.");
|
throw new IOException("Active pass required to download packs. Contact the administrator.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseCode != 200) {
|
if (responseCode != 200) {
|
||||||
@@ -391,34 +391,34 @@ public class PackDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Применить diff (скачать новые файлы, удалить старые)
|
* Apply diff (download new files, delete old ones)
|
||||||
*/
|
*/
|
||||||
private boolean applyDiff(DiffResponse diff, String packName) {
|
private boolean applyDiff(DiffResponse diff, String packName) {
|
||||||
System.out.println(ZAnsi.cyan("\nПрименение изменений:"));
|
System.out.println(ZAnsi.cyan("\nApplying changes:"));
|
||||||
System.out.println(" Загрузить: " + diff.getToDownload().size() + " файлов");
|
System.out.println(" Download: " + diff.getToDownload().size() + " files");
|
||||||
System.out.println(" Удалить: " + diff.getToDelete().size() + " файлов");
|
System.out.println(" Delete: " + diff.getToDelete().size() + " files");
|
||||||
|
|
||||||
// Создаем директории если нужно
|
// Create directories if needed
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(instance.getPath());
|
Files.createDirectories(instance.getPath());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.err.println(ZAnsi.red("Ошибка создания директорий: " + e.getMessage()));
|
System.err.println(ZAnsi.red("Error creating directories: " + e.getMessage()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Удаляем файлы
|
// Delete files
|
||||||
for (String filePath : diff.getToDelete()) {
|
for (String filePath : diff.getToDelete()) {
|
||||||
Path fullPath = instance.getPath().resolve(filePath);
|
Path fullPath = instance.getPath().resolve(filePath);
|
||||||
try {
|
try {
|
||||||
if (Files.deleteIfExists(fullPath)) {
|
if (Files.deleteIfExists(fullPath)) {
|
||||||
System.out.println(ZAnsi.yellow(" Удален: " + filePath));
|
System.out.println(ZAnsi.yellow(" Deleted: " + filePath));
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.err.println(ZAnsi.red(" Ошибка удаления " + filePath + ": " + e.getMessage()));
|
System.err.println(ZAnsi.red(" Error deleting " + filePath + ": " + e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Скачиваем файлы
|
// Download files
|
||||||
AtomicInteger downloaded = new AtomicInteger(0);
|
AtomicInteger downloaded = new AtomicInteger(0);
|
||||||
int total = diff.getToDownload().size();
|
int total = diff.getToDownload().size();
|
||||||
|
|
||||||
@@ -427,32 +427,32 @@ public class PackDownloader {
|
|||||||
Path fullPath = instance.getPath().resolve(path);
|
Path fullPath = instance.getPath().resolve(path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Создаем директории
|
// Create directories
|
||||||
Files.createDirectories(fullPath.getParent());
|
Files.createDirectories(fullPath.getParent());
|
||||||
|
|
||||||
// Скачиваем файл
|
// Download file
|
||||||
downloadFile(file, fullPath);
|
downloadFile(file, fullPath);
|
||||||
|
|
||||||
// Проверяем хеш
|
// Verify hash
|
||||||
String actualHash = calculateHash(fullPath);
|
String actualHash = calculateHash(fullPath);
|
||||||
if (!actualHash.equals(file.getHash())) {
|
if (!actualHash.equals(file.getHash())) {
|
||||||
throw new IOException("Хеш не совпадает! Ожидался: " + file.getHash() +
|
throw new IOException("Hash mismatch! Expected: " + file.getHash() +
|
||||||
", получен: " + actualHash);
|
", got: " + actualHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
downloaded.incrementAndGet();
|
downloaded.incrementAndGet();
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
ProgressBar.show("Скачивание", downloaded.get(), total, "файлов");
|
ProgressBar.show("Download", downloaded.get(), total, "files");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("\n" + ZAnsi.red(" Ошибка скачивания " + path + ": " + e.getMessage()));
|
System.err.println("\n" + ZAnsi.red(" Download error " + path + ": " + e.getMessage()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
ProgressBar.finish("Скачивание");
|
ProgressBar.finish("Download");
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
+24
-24
@@ -26,7 +26,7 @@ public class FabricInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean install(String minecraftVersion, String loaderVersion) throws Exception {
|
public boolean install(String minecraftVersion, String loaderVersion) throws Exception {
|
||||||
System.out.println(ZAnsi.cyan("Установка Fabric " + loaderVersion + " для Minecraft " + minecraftVersion));
|
System.out.println(ZAnsi.cyan("Installing Fabric " + loaderVersion + " for Minecraft " + minecraftVersion));
|
||||||
|
|
||||||
Path instancePath = instance.getPath();
|
Path instancePath = instance.getPath();
|
||||||
cleanOldFabricLoaders();
|
cleanOldFabricLoaders();
|
||||||
@@ -34,7 +34,7 @@ public class FabricInstaller {
|
|||||||
VersionInstaller versionInstaller = new VersionInstaller(instancePath);
|
VersionInstaller versionInstaller = new VersionInstaller(instancePath);
|
||||||
String assetIndex = versionInstaller.install(minecraftVersion);
|
String assetIndex = versionInstaller.install(minecraftVersion);
|
||||||
|
|
||||||
System.out.println(ZAnsi.green("Asset index получен: " + assetIndex));
|
System.out.println(ZAnsi.green("Asset index obtained: " + assetIndex));
|
||||||
|
|
||||||
instance.setAssetIndex(assetIndex);
|
instance.setAssetIndex(assetIndex);
|
||||||
instance.setMinecraftVersion(minecraftVersion);
|
instance.setMinecraftVersion(minecraftVersion);
|
||||||
@@ -46,12 +46,12 @@ public class FabricInstaller {
|
|||||||
Path installerJar = instancePath.resolve("fabric-installer.jar");
|
Path installerJar = instancePath.resolve("fabric-installer.jar");
|
||||||
|
|
||||||
if (!Files.exists(installerJar)) {
|
if (!Files.exists(installerJar)) {
|
||||||
ProgressBar.show("Скачивание Fabric Installer", 0, 100, "%");
|
ProgressBar.show("Downloading Fabric Installer", 0, 100, "%");
|
||||||
downloadFileWithFallback(installerUrl, installerJar);
|
downloadFileWithFallback(installerUrl, installerJar);
|
||||||
ProgressBar.finish("Fabric Installer скачан");
|
ProgressBar.finish("Fabric Installer downloaded");
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("Запуск Fabric Installer..."));
|
System.out.println(ZAnsi.cyan("Running Fabric Installer..."));
|
||||||
|
|
||||||
String fabricVersionId = "fabric-loader-" + loaderVersion + "-" + minecraftVersion;
|
String fabricVersionId = "fabric-loader-" + loaderVersion + "-" + minecraftVersion;
|
||||||
|
|
||||||
@@ -71,24 +71,24 @@ public class FabricInstaller {
|
|||||||
int exitCode = process.waitFor();
|
int exitCode = process.waitFor();
|
||||||
|
|
||||||
if (exitCode != 0) {
|
if (exitCode != 0) {
|
||||||
System.out.println(ZAnsi.brightRed("Fabric Installer завершился с ошибкой (код " + exitCode + ")"));
|
System.out.println(ZAnsi.brightRed("Fabric Installer failed (code " + exitCode + ")"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Path fabricVersionDir = instancePath.resolve("versions").resolve(fabricVersionId);
|
Path fabricVersionDir = instancePath.resolve("versions").resolve(fabricVersionId);
|
||||||
|
|
||||||
if (Files.exists(fabricVersionDir)) {
|
if (Files.exists(fabricVersionDir)) {
|
||||||
System.out.println(ZAnsi.brightGreen("Fabric успешно установлен!"));
|
System.out.println(ZAnsi.brightGreen("Fabric installed successfully!"));
|
||||||
|
|
||||||
instance.setLoaderType("fabric");
|
instance.setLoaderType("fabric");
|
||||||
instance.setLoaderVersion(loaderVersion);
|
instance.setLoaderVersion(loaderVersion);
|
||||||
instance.setFabricVersionId(fabricVersionId); // ← СОХРАНЯЕМ
|
instance.setFabricVersionId(fabricVersionId);
|
||||||
|
|
||||||
ensureAssetIndexInFabricVersion(fabricVersionDir, assetIndex);
|
ensureAssetIndexInFabricVersion(fabricVersionDir, assetIndex);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed("Fabric Installer отработал, но версия не найдена."));
|
System.out.println(ZAnsi.brightRed("Fabric Installer ran, but version not found."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ public class FabricInstaller {
|
|||||||
try {
|
try {
|
||||||
ZHttpClient.downloadFile(url, target);
|
ZHttpClient.downloadFile(url, target);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.yellow("Не удалось скачать Fabric Installer: " + e.getMessage()));
|
System.out.println(ZAnsi.yellow("Failed to download Fabric Installer: " + e.getMessage()));
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,28 +106,28 @@ public class FabricInstaller {
|
|||||||
Path versionJson = fabricVersionDir.resolve(fabricVersionDir.getFileName() + ".json");
|
Path versionJson = fabricVersionDir.resolve(fabricVersionDir.getFileName() + ".json");
|
||||||
|
|
||||||
if (!Files.exists(versionJson)) {
|
if (!Files.exists(versionJson)) {
|
||||||
System.out.println(ZAnsi.yellow("JSON файл версии не найден: " + versionJson));
|
System.out.println(ZAnsi.yellow("Version JSON file not found: " + versionJson));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String content = Files.readString(versionJson);
|
String content = Files.readString(versionJson);
|
||||||
|
|
||||||
// Проверяем и исправляем asset index
|
// Check and fix asset index
|
||||||
if (!content.contains("\"assets\":\"" + assetIndex + "\"")) {
|
if (!content.contains("\"assets\":\"" + assetIndex + "\"")) {
|
||||||
System.out.println(ZAnsi.yellow("Исправляем asset index в JSON файле версии..."));
|
System.out.println(ZAnsi.yellow("Fixing asset index in version JSON file..."));
|
||||||
|
|
||||||
// Заменяем assets на правильное значение
|
// Replace assets with correct value
|
||||||
content = content.replaceAll("\"assets\":\\s*\"[^\"]*\"", "\"assets\": \"" + assetIndex + "\"");
|
content = content.replaceAll("\"assets\":\\s*\"[^\"]*\"", "\"assets\": \"" + assetIndex + "\"");
|
||||||
|
|
||||||
// Также проверяем assetIndex
|
// Also check assetIndex
|
||||||
if (content.contains("\"assetIndex\"")) {
|
if (content.contains("\"assetIndex\"")) {
|
||||||
content = content.replaceAll("\"assetIndex\":\\s*\"[^\"]*\"", "\"assetIndex\": \"" + assetIndex + "\"");
|
content = content.replaceAll("\"assetIndex\":\\s*\"[^\"]*\"", "\"assetIndex\": \"" + assetIndex + "\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
Files.writeString(versionJson, content);
|
Files.writeString(versionJson, content);
|
||||||
System.out.println(ZAnsi.green("Asset index исправлен на: " + assetIndex));
|
System.out.println(ZAnsi.green("Asset index fixed to: " + assetIndex));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.green("Asset index в JSON версии правильный: " + assetIndex));
|
System.out.println(ZAnsi.green("Asset index in version JSON is correct: " + assetIndex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ public class FabricInstaller {
|
|||||||
Path librariesDir = instance.getPath().resolve("libraries/net/fabricmc/fabric-loader");
|
Path librariesDir = instance.getPath().resolve("libraries/net/fabricmc/fabric-loader");
|
||||||
if (!Files.exists(librariesDir)) return;
|
if (!Files.exists(librariesDir)) return;
|
||||||
|
|
||||||
System.out.println(ZAnsi.yellow("Очистка старых версий Fabric Loader..."));
|
System.out.println(ZAnsi.yellow("Cleaning old Fabric Loader versions..."));
|
||||||
|
|
||||||
try (var stream = Files.walk(librariesDir)) {
|
try (var stream = Files.walk(librariesDir)) {
|
||||||
stream.filter(Files::isDirectory)
|
stream.filter(Files::isDirectory)
|
||||||
@@ -155,18 +155,18 @@ public class FabricInstaller {
|
|||||||
|
|
||||||
private String getLatestInstallerVersion() throws Exception {
|
private String getLatestInstallerVersion() throws Exception {
|
||||||
try {
|
try {
|
||||||
// Используем ZHttpClient с умным прокси
|
// Use ZHttpClient with smart proxy
|
||||||
String xml = ZHttpClient.downloadString("https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml");
|
String xml = ZHttpClient.downloadString("https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml");
|
||||||
int start = xml.indexOf("<latest>") + 8;
|
int start = xml.indexOf("<latest>") + 8;
|
||||||
int end = xml.indexOf("</latest>", start);
|
int end = xml.indexOf("</latest>", start);
|
||||||
return xml.substring(start, end).trim();
|
return xml.substring(start, end).trim();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.yellow("Ошибка получения версии Fabric Installer: " + e.getMessage()));
|
System.out.println(ZAnsi.yellow("Error getting Fabric Installer version: " + e.getMessage()));
|
||||||
throw new Exception("Не удалось получить версию Fabric Installer", e);
|
throw new Exception("Failed to get Fabric Installer version", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// под рефактор оставить
|
// under refactor - keep
|
||||||
private String downloadString(String url) throws Exception {
|
private String downloadString(String url) throws Exception {
|
||||||
Exception lastException = null;
|
Exception lastException = null;
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@ public class FabricInstaller {
|
|||||||
throw new IOException("HTTP " + resp.statusCode());
|
throw new IOException("HTTP " + resp.statusCode());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
lastException = e;
|
lastException = e;
|
||||||
System.out.println(ZAnsi.yellow("Попытка " + attempt + " не удалась: " + e.getMessage()));
|
System.out.println(ZAnsi.yellow("Attempt " + attempt + " failed: " + e.getMessage()));
|
||||||
if (attempt < 3) {
|
if (attempt < 3) {
|
||||||
Thread.sleep(1000 * attempt);
|
Thread.sleep(1000 * attempt);
|
||||||
}
|
}
|
||||||
@@ -207,7 +207,7 @@ public class FabricInstaller {
|
|||||||
HttpResponse.BodyHandlers.ofFile(target));
|
HttpResponse.BodyHandlers.ofFile(target));
|
||||||
|
|
||||||
if (response.statusCode() != 200) {
|
if (response.statusCode() != 200) {
|
||||||
throw new IOException("HTTP " + response.statusCode() + " при скачивании " + url);
|
throw new IOException("HTTP " + response.statusCode() + " when downloading " + url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+38
-38
@@ -26,59 +26,59 @@ public class ForgeInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean install(String mcVersion, String forgeVersion) throws Exception {
|
public boolean install(String mcVersion, String forgeVersion) throws Exception {
|
||||||
System.out.println(ZAnsi.cyan("Установка Forge " + forgeVersion + " для Minecraft " + mcVersion));
|
System.out.println(ZAnsi.cyan("Installing Forge " + forgeVersion + " for Minecraft " + mcVersion));
|
||||||
|
|
||||||
// Шаг 1: Устанавливаем vanilla и получаем настоящий assetIndex
|
// Step 1: Install vanilla and get real assetIndex
|
||||||
System.out.println(ZAnsi.cyan("Установка базовой версии Minecraft " + mcVersion + "..."));
|
System.out.println(ZAnsi.cyan("Installing base Minecraft version " + mcVersion + "..."));
|
||||||
VersionInstaller vanillaInstaller = new VersionInstaller(instance.getPath());
|
VersionInstaller vanillaInstaller = new VersionInstaller(instance.getPath());
|
||||||
|
|
||||||
String assetIndex = vanillaInstaller.install(mcVersion);
|
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("Failed to install base Minecraft version"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
instance.setAssetIndex(assetIndex);
|
instance.setAssetIndex(assetIndex);
|
||||||
|
|
||||||
// Шаг 2: Создаём launcher_profiles.json
|
// Step 2: Create launcher_profiles.json
|
||||||
createLauncherProfile();
|
createLauncherProfile();
|
||||||
|
|
||||||
// Шаг 3: Скачиваем Forge Installer с прогресс-баром
|
// Step 3: Download Forge Installer with progress bar
|
||||||
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");
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("Скачивание Forge Installer..."));
|
System.out.println(ZAnsi.cyan("Downloading Forge Installer..."));
|
||||||
downloadFileWithProgress(installerUrl, installerJar);
|
downloadFileWithProgress(installerUrl, installerJar);
|
||||||
|
|
||||||
// Шаг 4: Запускаем Forge Installer и показываем его вывод
|
// Step 4: Run Forge Installer and show its output
|
||||||
System.out.println(ZAnsi.cyan("Запуск Forge Installer..."));
|
System.out.println(ZAnsi.cyan("Running Forge Installer..."));
|
||||||
System.out.println(ZAnsi.yellow("Это может занять несколько минут. Пожалуйста, подождите...\n"));
|
System.out.println(ZAnsi.yellow("This may take a few minutes. Please wait...\n"));
|
||||||
|
|
||||||
boolean success = runForgeInstaller(installerJar);
|
boolean success = runForgeInstaller(installerJar);
|
||||||
|
|
||||||
// После успешной установки Forge, но перед сохранением метаданных
|
// After successful Forge install, before saving metadata
|
||||||
if (success) {
|
if (success) {
|
||||||
// Докачиваем пропущенные библиотеки
|
// Download missing libraries
|
||||||
try {
|
try {
|
||||||
downloadMissingLibraries(mcVersion, forgeVersion);
|
downloadMissingLibraries(mcVersion, forgeVersion);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.yellow("Предупреждение: не удалось докачать некоторые библиотеки: " + e.getMessage()));
|
System.out.println(ZAnsi.yellow("Warning: could not download some libraries: " + e.getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println(ZAnsi.brightGreen("\nForge " + forgeVersion + " успешно установлен!"));
|
System.out.println(ZAnsi.brightGreen("\nForge " + forgeVersion + " installed successfully!"));
|
||||||
instance.setMinecraftVersion(mcVersion);
|
instance.setMinecraftVersion(mcVersion);
|
||||||
instance.setLoaderType("forge");
|
instance.setLoaderType("forge");
|
||||||
instance.setLoaderVersion(forgeVersion);
|
instance.setLoaderVersion(forgeVersion);
|
||||||
|
|
||||||
// Очищаем временный файл установщика
|
// Clean up temporary installer file
|
||||||
Files.deleteIfExists(installerJar);
|
Files.deleteIfExists(installerJar);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed("\nОшибка при установке Forge!"));
|
System.out.println(ZAnsi.brightRed("\nError installing Forge!"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ public class ForgeInstaller {
|
|||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
Files.writeString(profilePath, minimalProfile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
Files.writeString(profilePath, minimalProfile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
System.out.println(ZAnsi.yellow("Создан launcher_profiles.json"));
|
System.out.println(ZAnsi.yellow("Created launcher_profiles.json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void downloadFileWithProgress(String url, Path target) throws Exception {
|
private void downloadFileWithProgress(String url, Path target) throws Exception {
|
||||||
@@ -132,10 +132,10 @@ public class ForgeInstaller {
|
|||||||
lastPercent = percent;
|
lastPercent = percent;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Если размер неизвестен, показываем анимацию
|
// If size unknown, show animation
|
||||||
char[] spinner = {'|', '/', '-', '\\'};
|
char[] spinner = {'|', '/', '-', '\\'};
|
||||||
int idx = (int) (totalRead / 1024) % 4;
|
int idx = (int) (totalRead / 1024) % 4;
|
||||||
System.out.print("\rСкачивание Forge Installer: " + ProgressBar.formatBytes(totalRead) + " " + spinner[idx]);
|
System.out.print("\rDownloading Forge Installer: " + ProgressBar.formatBytes(totalRead) + " " + spinner[idx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,12 +144,12 @@ public class ForgeInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean runForgeInstaller(Path installerJar) throws IOException, InterruptedException {
|
private boolean runForgeInstaller(Path installerJar) throws IOException, InterruptedException {
|
||||||
// Пробуем до 3 раз с разными опциями
|
// Try up to 3 times with different options
|
||||||
int maxRetries = 3;
|
int maxRetries = 3;
|
||||||
int attempt = 1;
|
int attempt = 1;
|
||||||
|
|
||||||
while (attempt <= maxRetries) {
|
while (attempt <= maxRetries) {
|
||||||
System.out.println(ZAnsi.cyan("Попытка " + attempt + " из " + maxRetries));
|
System.out.println(ZAnsi.cyan("Attempt " + attempt + " of " + maxRetries));
|
||||||
|
|
||||||
ProcessBuilder pb = new ProcessBuilder(
|
ProcessBuilder pb = new ProcessBuilder(
|
||||||
"java",
|
"java",
|
||||||
@@ -158,7 +158,7 @@ public class ForgeInstaller {
|
|||||||
"--installClient"
|
"--installClient"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Добавляем JVM аргументы для увеличения таймаутов
|
// Add JVM args for increased timeouts
|
||||||
pb.environment().put("JAVA_OPTS", "-Dhttp.connectionTimeout=60000 -Dhttp.socketTimeout=60000");
|
pb.environment().put("JAVA_OPTS", "-Dhttp.connectionTimeout=60000 -Dhttp.socketTimeout=60000");
|
||||||
|
|
||||||
pb.directory(instance.getPath().toFile());
|
pb.directory(instance.getPath().toFile());
|
||||||
@@ -166,7 +166,7 @@ public class ForgeInstaller {
|
|||||||
|
|
||||||
Process process = pb.start();
|
Process process = pb.start();
|
||||||
|
|
||||||
// Читаем вывод в реальном времени
|
// Read output in real time
|
||||||
StringBuilder output = new StringBuilder();
|
StringBuilder output = new StringBuilder();
|
||||||
boolean hasErrors = false;
|
boolean hasErrors = false;
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ public class ForgeInstaller {
|
|||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
output.append(line).append("\n");
|
output.append(line).append("\n");
|
||||||
|
|
||||||
// Форматируем вывод Forge Installer
|
// Format Forge Installer output
|
||||||
if (line.contains("Downloading") || line.contains("Extracting")) {
|
if (line.contains("Downloading") || line.contains("Extracting")) {
|
||||||
System.out.println(ZAnsi.blue(" -> " + line));
|
System.out.println(ZAnsi.blue(" -> " + line));
|
||||||
} else if (line.contains("SUCCESS") || line.contains("successfully")) {
|
} else if (line.contains("SUCCESS") || line.contains("successfully")) {
|
||||||
@@ -195,17 +195,17 @@ public class ForgeInstaller {
|
|||||||
|
|
||||||
int exitCode = process.waitFor();
|
int exitCode = process.waitFor();
|
||||||
|
|
||||||
// Если успешно или нет ошибок скачивания
|
// If successful or no download errors
|
||||||
if (exitCode == 0 && !hasErrors) {
|
if (exitCode == 0 && !hasErrors) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если ошибка и это не последняя попытка
|
// If error and not last attempt
|
||||||
if (attempt < maxRetries) {
|
if (attempt < maxRetries) {
|
||||||
System.out.println(ZAnsi.yellow("Ошибка при установке. Повторная попытка через 5 секунд..."));
|
System.out.println(ZAnsi.yellow("Install error. Retrying in 5 seconds..."));
|
||||||
Thread.sleep(5000);
|
Thread.sleep(5000);
|
||||||
|
|
||||||
// Очищаем временные файлы перед повтором
|
// Clean temp files before retry
|
||||||
Path librariesDir = instance.getPath().resolve("libraries");
|
Path librariesDir = instance.getPath().resolve("libraries");
|
||||||
if (Files.exists(librariesDir)) {
|
if (Files.exists(librariesDir)) {
|
||||||
// Удаляем только частично скачанные библиотеки Forge
|
// Удаляем только частично скачанные библиотеки Forge
|
||||||
@@ -218,15 +218,15 @@ public class ForgeInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed("Forge Installer завершился с кодом ошибки: " + exitCode));
|
System.out.println(ZAnsi.brightRed("Forge Installer exited with error code: " + exitCode));
|
||||||
|
|
||||||
// Показываем возможное решение
|
// Show possible solution
|
||||||
if (output.toString().contains("timed out")) {
|
if (output.toString().contains("timed out")) {
|
||||||
System.out.println(ZAnsi.yellow("\nВозможные решения:"));
|
System.out.println(ZAnsi.yellow("\nPossible solutions:"));
|
||||||
System.out.println(ZAnsi.yellow("1. Проверьте интернет-соединение"));
|
System.out.println(ZAnsi.yellow("1. Check your internet connection"));
|
||||||
System.out.println(ZAnsi.yellow("2. Запустите лаунчер от имени администратора"));
|
System.out.println(ZAnsi.yellow("2. Run the launcher as administrator"));
|
||||||
System.out.println(ZAnsi.yellow("3. Временно отключите антивирус/брандмауэр"));
|
System.out.println(ZAnsi.yellow("3. Temporarily disable antivirus/firewall"));
|
||||||
System.out.println(ZAnsi.yellow("4. Попробуйте установить другую версию Forge"));
|
System.out.println(ZAnsi.yellow("4. Try installing a different Forge version"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,9 +237,9 @@ public class ForgeInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void downloadMissingLibraries(String mcVersion, String forgeVersion) throws Exception {
|
private void downloadMissingLibraries(String mcVersion, String forgeVersion) throws Exception {
|
||||||
System.out.println(ZAnsi.cyan("Проверка и докачка отсутствующих библиотек..."));
|
System.out.println(ZAnsi.cyan("Checking and downloading missing libraries..."));
|
||||||
|
|
||||||
// Список проблемных библиотек и их альтернативные URL
|
// List of problematic libraries and their alternate URLs
|
||||||
Map<String, String> alternativeUrls = new HashMap<>();
|
Map<String, String> alternativeUrls = new HashMap<>();
|
||||||
alternativeUrls.put("org/ow2/asm/asm/9.6/asm-9.6.jar",
|
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");
|
"https://repo1.maven.org/maven2/org/ow2/asm/asm/9.6/asm-9.6.jar");
|
||||||
@@ -252,7 +252,7 @@ public class ForgeInstaller {
|
|||||||
Path target = librariesDir.resolve(entry.getKey());
|
Path target = librariesDir.resolve(entry.getKey());
|
||||||
if (!Files.exists(target)) {
|
if (!Files.exists(target)) {
|
||||||
Files.createDirectories(target.getParent());
|
Files.createDirectories(target.getParent());
|
||||||
System.out.println(ZAnsi.yellow("Докачка: " + target.getFileName()));
|
System.out.println(ZAnsi.yellow("Downloading: " + target.getFileName()));
|
||||||
|
|
||||||
for (int attempt = 1; attempt <= 3; attempt++) {
|
for (int attempt = 1; attempt <= 3; attempt++) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+22
-22
@@ -27,14 +27,14 @@ public class NeoForgeInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean install(String mcVersion, String neoForgeVersion) throws Exception {
|
public boolean install(String mcVersion, String neoForgeVersion) throws Exception {
|
||||||
System.out.println(ZAnsi.cyan("Установка NeoForge " + neoForgeVersion + " для Minecraft " + mcVersion));
|
System.out.println(ZAnsi.cyan("Installing NeoForge " + neoForgeVersion + " for Minecraft " + mcVersion));
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("Установка базовой версии Minecraft " + mcVersion + "..."));
|
System.out.println(ZAnsi.cyan("Installing base Minecraft version " + mcVersion + "..."));
|
||||||
VersionInstaller vanillaInstaller = new VersionInstaller(instance.getPath());
|
VersionInstaller vanillaInstaller = new VersionInstaller(instance.getPath());
|
||||||
String assetIndex = vanillaInstaller.install(mcVersion);
|
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("Failed to install base Minecraft version"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,11 +52,11 @@ public class NeoForgeInstaller {
|
|||||||
|
|
||||||
Path installerJar = instance.getPath().resolve("neoforge-installer.jar");
|
Path installerJar = instance.getPath().resolve("neoforge-installer.jar");
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("Скачивание NeoForge Installer..."));
|
System.out.println(ZAnsi.cyan("Downloading NeoForge Installer..."));
|
||||||
downloadFileWithProgress(installerUrl, installerJar);
|
downloadFileWithProgress(installerUrl, installerJar);
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("Запуск NeoForge Installer..."));
|
System.out.println(ZAnsi.cyan("Running NeoForge Installer..."));
|
||||||
System.out.println(ZAnsi.yellow("Это может занять несколько минут. Пожалуйста, подождите...\n"));
|
System.out.println(ZAnsi.yellow("This may take a few minutes. Please wait...\n"));
|
||||||
|
|
||||||
boolean success = runNeoForgeInstaller(installerJar);
|
boolean success = runNeoForgeInstaller(installerJar);
|
||||||
|
|
||||||
@@ -64,10 +64,10 @@ public class NeoForgeInstaller {
|
|||||||
try {
|
try {
|
||||||
downloadMissingLibraries(mcVersion, neoForgeVersion, mavenGroup, mavenArtifact);
|
downloadMissingLibraries(mcVersion, neoForgeVersion, mavenGroup, mavenArtifact);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.yellow("Предупреждение: не удалось докачать некоторые библиотеки: " + e.getMessage()));
|
System.out.println(ZAnsi.yellow("Warning: could not download some libraries: " + e.getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println(ZAnsi.brightGreen("\nNeoForge " + neoForgeVersion + " успешно установлен!"));
|
System.out.println(ZAnsi.brightGreen("\nNeoForge " + neoForgeVersion + " installed successfully!"));
|
||||||
instance.setMinecraftVersion(mcVersion);
|
instance.setMinecraftVersion(mcVersion);
|
||||||
instance.setLoaderType("neoforge");
|
instance.setLoaderType("neoforge");
|
||||||
instance.setLoaderVersion(neoForgeVersion);
|
instance.setLoaderVersion(neoForgeVersion);
|
||||||
@@ -75,7 +75,7 @@ public class NeoForgeInstaller {
|
|||||||
Files.deleteIfExists(installerJar);
|
Files.deleteIfExists(installerJar);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed("\nОшибка при установке NeoForge!"));
|
System.out.println(ZAnsi.brightRed("\nError installing NeoForge!"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ public class NeoForgeInstaller {
|
|||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
Files.writeString(profilePath, minimalProfile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
Files.writeString(profilePath, minimalProfile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
System.out.println(ZAnsi.yellow("Создан launcher_profiles.json"));
|
System.out.println(ZAnsi.yellow("Created launcher_profiles.json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void downloadFileWithProgress(String url, Path target) throws Exception {
|
private void downloadFileWithProgress(String url, Path target) throws Exception {
|
||||||
@@ -145,7 +145,7 @@ public class NeoForgeInstaller {
|
|||||||
} else {
|
} else {
|
||||||
char[] spinner = {'|', '/', '-', '\\'};
|
char[] spinner = {'|', '/', '-', '\\'};
|
||||||
int idx = (int) (totalRead / 1024) % 4;
|
int idx = (int) (totalRead / 1024) % 4;
|
||||||
System.out.print("\rСкачивание NeoForge Installer: " + ProgressBar.formatBytes(totalRead) + " " + spinner[idx]);
|
System.out.print("\rDownloading NeoForge Installer: " + ProgressBar.formatBytes(totalRead) + " " + spinner[idx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,7 +158,7 @@ public class NeoForgeInstaller {
|
|||||||
int attempt = 1;
|
int attempt = 1;
|
||||||
|
|
||||||
while (attempt <= maxRetries) {
|
while (attempt <= maxRetries) {
|
||||||
System.out.println(ZAnsi.cyan("Попытка " + attempt + " из " + maxRetries));
|
System.out.println(ZAnsi.cyan("Attempt " + attempt + " of " + maxRetries));
|
||||||
|
|
||||||
ProcessBuilder pb = new ProcessBuilder(
|
ProcessBuilder pb = new ProcessBuilder(
|
||||||
"java",
|
"java",
|
||||||
@@ -205,7 +205,7 @@ public class NeoForgeInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (attempt < maxRetries) {
|
if (attempt < maxRetries) {
|
||||||
System.out.println(ZAnsi.yellow("Ошибка при установке. Повторная попытка через 5 секунд..."));
|
System.out.println(ZAnsi.yellow("Install error. Retrying in 5 seconds..."));
|
||||||
Thread.sleep(5000);
|
Thread.sleep(5000);
|
||||||
|
|
||||||
Path librariesDir = instance.getPath().resolve("libraries");
|
Path librariesDir = instance.getPath().resolve("libraries");
|
||||||
@@ -219,14 +219,14 @@ public class NeoForgeInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.brightRed("NeoForge Installer завершился с кодом ошибки: " + exitCode));
|
System.out.println(ZAnsi.brightRed("NeoForge Installer exited with error code: " + exitCode));
|
||||||
|
|
||||||
if (output.toString().contains("timed out")) {
|
if (output.toString().contains("timed out")) {
|
||||||
System.out.println(ZAnsi.yellow("\nВозможные решения:"));
|
System.out.println(ZAnsi.yellow("\nPossible solutions:"));
|
||||||
System.out.println(ZAnsi.yellow("1. Проверьте интернет-соединение"));
|
System.out.println(ZAnsi.yellow("1. Check your internet connection"));
|
||||||
System.out.println(ZAnsi.yellow("2. Запустите лаунчер от имени администратора"));
|
System.out.println(ZAnsi.yellow("2. Run the launcher as administrator"));
|
||||||
System.out.println(ZAnsi.yellow("3. Временно отключите антивирус/брандмауэр"));
|
System.out.println(ZAnsi.yellow("3. Temporarily disable antivirus/firewall"));
|
||||||
System.out.println(ZAnsi.yellow("4. Попробуйте установить другую версию NeoForge"));
|
System.out.println(ZAnsi.yellow("4. Try installing a different NeoForge version"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ public class NeoForgeInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void downloadMissingLibraries(String mcVersion, String neoForgeVersion, String mavenGroup, String mavenArtifact) throws Exception {
|
private void downloadMissingLibraries(String mcVersion, String neoForgeVersion, String mavenGroup, String mavenArtifact) throws Exception {
|
||||||
System.out.println(ZAnsi.cyan("Проверка и докачка отсутствующих библиотек..."));
|
System.out.println(ZAnsi.cyan("Checking and downloading missing libraries..."));
|
||||||
|
|
||||||
Map<String, String> alternativeUrls = new HashMap<>();
|
Map<String, String> alternativeUrls = new HashMap<>();
|
||||||
alternativeUrls.put("org/ow2/asm/asm/9.6/asm-9.6.jar",
|
alternativeUrls.put("org/ow2/asm/asm/9.6/asm-9.6.jar",
|
||||||
@@ -253,7 +253,7 @@ public class NeoForgeInstaller {
|
|||||||
Path target = librariesDir.resolve(entry.getKey());
|
Path target = librariesDir.resolve(entry.getKey());
|
||||||
if (!Files.exists(target)) {
|
if (!Files.exists(target)) {
|
||||||
Files.createDirectories(target.getParent());
|
Files.createDirectories(target.getParent());
|
||||||
System.out.println(ZAnsi.yellow("Докачка: " + target.getFileName()));
|
System.out.println(ZAnsi.yellow("Downloading: " + target.getFileName()));
|
||||||
|
|
||||||
for (int attempt = 1; attempt <= 3; attempt++) {
|
for (int attempt = 1; attempt <= 3; attempt++) {
|
||||||
try {
|
try {
|
||||||
@@ -261,7 +261,7 @@ public class NeoForgeInstaller {
|
|||||||
break;
|
break;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (attempt == 3) throw e;
|
if (attempt == 3) throw e;
|
||||||
System.out.println(ZAnsi.yellow("Повторная попытка " + attempt + "/3..."));
|
System.out.println(ZAnsi.yellow("Retry " + attempt + "/3..."));
|
||||||
Thread.sleep(2000);
|
Thread.sleep(2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-26
@@ -57,12 +57,12 @@ public class VersionInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String install(String versionId) throws Exception {
|
public String install(String versionId) throws Exception {
|
||||||
System.out.println(ZAnsi.cyan("Полная установка Minecraft " + versionId + "..."));
|
System.out.println(ZAnsi.cyan("Full install of Minecraft " + versionId + "..."));
|
||||||
Path versionDir = minecraftDir.resolve("versions").resolve(versionId);
|
Path versionDir = minecraftDir.resolve("versions").resolve(versionId);
|
||||||
Files.createDirectories(versionDir);
|
Files.createDirectories(versionDir);
|
||||||
|
|
||||||
String versionUrl = getVersionUrl(versionId);
|
String versionUrl = getVersionUrl(versionId);
|
||||||
if (versionUrl == null) throw new Exception("Версия " + versionId + " не найдена");
|
if (versionUrl == null) throw new Exception("Version " + versionId + " not found");
|
||||||
|
|
||||||
String versionJson = downloadString(versionUrl);
|
String versionJson = downloadString(versionUrl);
|
||||||
Files.writeString(versionDir.resolve(versionId + ".json"), versionJson);
|
Files.writeString(versionDir.resolve(versionId + ".json"), versionJson);
|
||||||
@@ -73,8 +73,8 @@ public class VersionInstaller {
|
|||||||
downloadFile(versionData.getJSONObject("downloads").getJSONObject("client").getString("url"),
|
downloadFile(versionData.getJSONObject("downloads").getJSONObject("client").getString("url"),
|
||||||
versionDir.resolve(versionId + ".jar"), "client.jar");
|
versionDir.resolve(versionId + ".jar"), "client.jar");
|
||||||
|
|
||||||
// Библиотеки
|
// Libraries
|
||||||
System.out.println(ZAnsi.cyan("Скачивание библиотек..."));
|
System.out.println(ZAnsi.cyan("Downloading libraries..."));
|
||||||
downloadLibraries(versionData.getJSONArray("libraries"));
|
downloadLibraries(versionData.getJSONArray("libraries"));
|
||||||
|
|
||||||
String assetIndex;
|
String assetIndex;
|
||||||
@@ -86,12 +86,12 @@ public class VersionInstaller {
|
|||||||
|
|
||||||
System.out.println(ZAnsi.cyan("Asset index: " + assetIndex));
|
System.out.println(ZAnsi.cyan("Asset index: " + assetIndex));
|
||||||
|
|
||||||
// Скачиваем ассеты используя правильный индекс
|
// Download assets using correct index
|
||||||
System.out.println(ZAnsi.cyan("Скачивание ассетов..."));
|
System.out.println(ZAnsi.cyan("Downloading assets..."));
|
||||||
downloadAssets(versionData, assetIndex);
|
downloadAssets(versionData, assetIndex);
|
||||||
|
|
||||||
System.out.println(ZAnsi.brightGreen("\nMinecraft " + versionId + " полностью установлен!"));
|
System.out.println(ZAnsi.brightGreen("\nMinecraft " + versionId + " fully installed!"));
|
||||||
return assetIndex; // ← возвращаем "5" а не "1.20.1"
|
return assetIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void downloadLibraries(JSONArray libraries) throws Exception {
|
private void downloadLibraries(JSONArray libraries) throws Exception {
|
||||||
@@ -111,32 +111,32 @@ public class VersionInstaller {
|
|||||||
try {
|
try {
|
||||||
downloadFile(url, target, "library");
|
downloadFile(url, target, "library");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Пропускаем проблемные библиотеки
|
// Skip problematic libraries
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count++;
|
count++;
|
||||||
ProgressBar.show("Библиотеки", count, total, "файлов");
|
ProgressBar.show("Libraries", count, total, "files");
|
||||||
}
|
}
|
||||||
ProgressBar.finish("Библиотеки загружены");
|
ProgressBar.finish("Libraries downloaded");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void downloadAssets(JSONObject versionData, String assetIndex) throws Exception {
|
private void downloadAssets(JSONObject versionData, String assetIndex) throws Exception {
|
||||||
// Находим URL для asset index
|
// Find URL for asset index
|
||||||
JSONObject assetIndexInfo = versionData.getJSONObject("assetIndex");
|
JSONObject assetIndexInfo = versionData.getJSONObject("assetIndex");
|
||||||
String indexUrl = assetIndexInfo.getString("url");
|
String indexUrl = assetIndexInfo.getString("url");
|
||||||
|
|
||||||
Path indexesDir = minecraftDir.resolve("assets/indexes");
|
Path indexesDir = minecraftDir.resolve("assets/indexes");
|
||||||
Files.createDirectories(indexesDir);
|
Files.createDirectories(indexesDir);
|
||||||
Path indexPath = indexesDir.resolve(assetIndex + ".json"); // ← используем assetIndex
|
Path indexPath = indexesDir.resolve(assetIndex + ".json");
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("Скачивание asset index (" + assetIndex + ")..."));
|
System.out.println(ZAnsi.cyan("Downloading asset index (" + assetIndex + ")..."));
|
||||||
downloadFile(indexUrl, indexPath, "asset index");
|
downloadFile(indexUrl, indexPath, "asset index");
|
||||||
|
|
||||||
String jsonContent = Files.readString(indexPath);
|
String jsonContent = Files.readString(indexPath);
|
||||||
JSONObject root = new JSONObject(jsonContent);
|
JSONObject root = new JSONObject(jsonContent);
|
||||||
JSONObject objects = root.getJSONObject("objects");
|
JSONObject objects = root.getJSONObject("objects");
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("Скачивание " + objects.length() + " объектов ассетов (index: " + assetIndex + ")..."));
|
System.out.println(ZAnsi.cyan("Downloading " + objects.length() + " asset objects (index: " + assetIndex + ")..."));
|
||||||
|
|
||||||
int total = objects.length();
|
int total = objects.length();
|
||||||
int[] success = {0};
|
int[] success = {0};
|
||||||
@@ -146,7 +146,7 @@ public class VersionInstaller {
|
|||||||
|
|
||||||
for (String key : objects.keySet()) {
|
for (String key : objects.keySet()) {
|
||||||
JSONObject asset = objects.getJSONObject(key);
|
JSONObject asset = objects.getJSONObject(key);
|
||||||
String hash = asset.getString("hash"); // ← вот это правильный хеш!
|
String hash = asset.getString("hash");
|
||||||
|
|
||||||
String url = "https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash;
|
String url = "https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash;
|
||||||
Path target = minecraftDir.resolve("assets/objects")
|
Path target = minecraftDir.resolve("assets/objects")
|
||||||
@@ -162,7 +162,7 @@ public class VersionInstaller {
|
|||||||
downloadFile(url, target, "");
|
downloadFile(url, target, "");
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
success[0]++;
|
success[0]++;
|
||||||
ProgressBar.show("Ассеты", success[0], total, "файлов");
|
ProgressBar.show("Assets", success[0], total, "files");
|
||||||
}
|
}
|
||||||
downloaded = true;
|
downloaded = true;
|
||||||
break;
|
break;
|
||||||
@@ -171,7 +171,7 @@ public class VersionInstaller {
|
|||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
failed[0]++;
|
failed[0]++;
|
||||||
}
|
}
|
||||||
System.err.println("Не удалось скачать " + hash);
|
System.err.println("Failed to download " + hash);
|
||||||
} else {
|
} else {
|
||||||
try { Thread.sleep(500 * attempt); } catch (InterruptedException ignored) {}
|
try { Thread.sleep(500 * attempt); } catch (InterruptedException ignored) {}
|
||||||
}
|
}
|
||||||
@@ -184,17 +184,17 @@ public class VersionInstaller {
|
|||||||
|
|
||||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||||
|
|
||||||
ProgressBar.finish("Ассеты загружены (" + success[0] + " успешно, " + failed[0] + " пропущено)");
|
ProgressBar.finish("Assets downloaded (" + success[0] + " ok, " + failed[0] + " skipped)");
|
||||||
|
|
||||||
if (failed[0] > 0) {
|
if (failed[0] > 0) {
|
||||||
System.out.println(ZAnsi.yellow("Предупреждение: " + failed[0] + " файлов ассетов не удалось скачать."));
|
System.out.println(ZAnsi.yellow("Warning: " + failed[0] + " asset files could not be downloaded."));
|
||||||
System.out.println(ZAnsi.yellow("Игра запустится, но некоторые текстуры/звуки могут отсутствовать."));
|
System.out.println(ZAnsi.yellow("The game will launch, but some textures/sounds may be missing."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAssetIndexId(String versionId) throws Exception {
|
public String getAssetIndexId(String versionId) throws Exception {
|
||||||
String versionUrl = getVersionUrl(versionId);
|
String versionUrl = getVersionUrl(versionId);
|
||||||
if (versionUrl == null) throw new Exception("Версия не найдена");
|
if (versionUrl == null) throw new Exception("Version not found");
|
||||||
|
|
||||||
String versionJson = downloadString(versionUrl);
|
String versionJson = downloadString(versionUrl);
|
||||||
JSONObject versionData = new JSONObject(versionJson);
|
JSONObject versionData = new JSONObject(versionJson);
|
||||||
@@ -202,7 +202,7 @@ public class VersionInstaller {
|
|||||||
if (versionData.has("assetIndex") && versionData.getJSONObject("assetIndex").has("id")) {
|
if (versionData.has("assetIndex") && versionData.getJSONObject("assetIndex").has("id")) {
|
||||||
return versionData.getJSONObject("assetIndex").getString("id"); // "5" для 1.20.1
|
return versionData.getJSONObject("assetIndex").getString("id"); // "5" для 1.20.1
|
||||||
}
|
}
|
||||||
return versionData.getString("assets"); // fallback (очень старые версии)
|
return versionData.getString("assets"); // fallback (very old versions)
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getVersionUrl(String versionId) throws Exception {
|
private String getVersionUrl(String versionId) throws Exception {
|
||||||
@@ -222,7 +222,7 @@ public class VersionInstaller {
|
|||||||
private void downloadFile(String url, Path target, String label) throws Exception {
|
private void downloadFile(String url, Path target, String label) throws Exception {
|
||||||
if (!label.isEmpty()) {
|
if (!label.isEmpty()) {
|
||||||
ProgressBar.clearLine();
|
ProgressBar.clearLine();
|
||||||
System.out.println(ZAnsi.cyan("Скачивание " + label + "..."));
|
System.out.println(ZAnsi.cyan("Downloading " + label + "..."));
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
@@ -233,8 +233,8 @@ public class VersionInstaller {
|
|||||||
HttpResponse<Path> response = httpClient.send(request, HttpResponse.BodyHandlers.ofFile(target));
|
HttpResponse<Path> response = httpClient.send(request, HttpResponse.BodyHandlers.ofFile(target));
|
||||||
|
|
||||||
if (response.statusCode() != 200) {
|
if (response.statusCode() != 200) {
|
||||||
if (label.isEmpty()) return; // для ассетов молча
|
if (label.isEmpty()) return; // for assets silently
|
||||||
throw new IOException("HTTP " + response.statusCode() + " при скачивании " + label);
|
throw new IOException("HTTP " + response.statusCode() + " while downloading " + label);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!label.isEmpty()) {
|
if (!label.isEmpty()) {
|
||||||
|
|||||||
+10
-9
@@ -21,11 +21,12 @@ public class LaunchCommandBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<String> build(LaunchOptions options) throws Exception {
|
public List<String> build(LaunchOptions options) throws Exception {
|
||||||
System.out.println(ZAnsi.cyan("Генерация команды запуска для " + instance.getName() + "..."));
|
System.out.println(ZAnsi.cyan("Generating launch command for " + instance.getName() + "..."));
|
||||||
|
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
|
|
||||||
String javaPath = "java";
|
String javaPath = options.getJavaPath() != null && !options.getJavaPath().isEmpty()
|
||||||
|
? options.getJavaPath() : "java";
|
||||||
command.add(javaPath);
|
command.add(javaPath);
|
||||||
|
|
||||||
command.addAll(getJvmArguments(options));
|
command.addAll(getJvmArguments(options));
|
||||||
@@ -53,7 +54,7 @@ public class LaunchCommandBuilder {
|
|||||||
|
|
||||||
// Fallback if classpath is empty
|
// Fallback if classpath is empty
|
||||||
if (classpath.isEmpty() || classpath.equals(instance.getPath().resolve("versions").resolve(getVersionId()).resolve(getVersionId() + ".jar").toAbsolutePath().toString())) {
|
if (classpath.isEmpty() || classpath.equals(instance.getPath().resolve("versions").resolve(getVersionId()).resolve(getVersionId() + ".jar").toAbsolutePath().toString())) {
|
||||||
System.out.println(ZAnsi.yellow(" manifest classpath пустой, использую vanilla classpath"));
|
System.out.println(ZAnsi.yellow(" manifest classpath empty, using vanilla classpath"));
|
||||||
command.add("-cp");
|
command.add("-cp");
|
||||||
command.add(buildVanillaClasspath());
|
command.add(buildVanillaClasspath());
|
||||||
command.add(getVanillaMainClass());
|
command.add(getVanillaMainClass());
|
||||||
@@ -83,15 +84,15 @@ public class LaunchCommandBuilder {
|
|||||||
if (versionJson != null && Files.exists(versionJson)) {
|
if (versionJson != null && Files.exists(versionJson)) {
|
||||||
String content = Files.readString(versionJson);
|
String content = Files.readString(versionJson);
|
||||||
JSONObject json = new JSONObject(content);
|
JSONObject json = new JSONObject(content);
|
||||||
System.out.println(ZAnsi.green("Найден version.json: " + versionJson.getFileName()));
|
System.out.println(ZAnsi.green("Found version.json: " + versionJson.getFileName()));
|
||||||
return new VersionManifest(json);
|
return new VersionManifest(json);
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.yellow("version.json не найден для " + instance.getName()));
|
System.out.println(ZAnsi.yellow("version.json not found for " + instance.getName()));
|
||||||
System.out.println(ZAnsi.yellow(" loaderType=" + instance.getLoaderType() + " mcVersion=" + instance.getMinecraftVersion() + " loaderVersion=" + instance.getLoaderVersion()));
|
System.out.println(ZAnsi.yellow(" loaderType=" + instance.getLoaderType() + " mcVersion=" + instance.getMinecraftVersion() + " loaderVersion=" + instance.getLoaderVersion()));
|
||||||
System.out.println(ZAnsi.yellow(" path=" + instance.getPath()));
|
System.out.println(ZAnsi.yellow(" path=" + instance.getPath()));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println(ZAnsi.yellow("Не удалось загрузить version.json: " + e.getMessage()));
|
System.out.println(ZAnsi.yellow("Failed to load version.json: " + e.getMessage()));
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -251,9 +252,9 @@ public class LaunchCommandBuilder {
|
|||||||
String assetIndex = instance.getAssetIndex();
|
String assetIndex = instance.getAssetIndex();
|
||||||
if (assetIndex == null || assetIndex.isEmpty()) {
|
if (assetIndex == null || assetIndex.isEmpty()) {
|
||||||
assetIndex = instance.getMinecraftVersion();
|
assetIndex = instance.getMinecraftVersion();
|
||||||
System.out.println(ZAnsi.yellow("Asset index не найден, использую версию: " + assetIndex));
|
System.out.println(ZAnsi.yellow("Asset index not found, using version: " + assetIndex));
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.green("Использую asset index: " + assetIndex));
|
System.out.println(ZAnsi.green("Using asset index: " + assetIndex));
|
||||||
}
|
}
|
||||||
args.add(assetIndex);
|
args.add(assetIndex);
|
||||||
args.add("--username");
|
args.add("--username");
|
||||||
@@ -332,7 +333,7 @@ public class LaunchCommandBuilder {
|
|||||||
if (Files.exists(fallbackPath)) {
|
if (Files.exists(fallbackPath)) {
|
||||||
paths.add(fallbackPath.toAbsolutePath().toString());
|
paths.add(fallbackPath.toAbsolutePath().toString());
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.yellow(" Библиотека не найдена: " + lib.name));
|
System.out.println(ZAnsi.yellow(" Library not found: " + lib.name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -37,5 +37,7 @@ public class LaunchOptions {
|
|||||||
public void setExtraJvmArgs(List<String> extraJvmArgs) { this.extraJvmArgs = extraJvmArgs; }
|
public void setExtraJvmArgs(List<String> extraJvmArgs) { this.extraJvmArgs = extraJvmArgs; }
|
||||||
|
|
||||||
public int getWidth() { return width; }
|
public int getWidth() { return width; }
|
||||||
|
public void setWidth(int width) { this.width = width; }
|
||||||
public int getHeight() { return height; }
|
public int getHeight() { return height; }
|
||||||
|
public void setHeight(int height) { this.height = height; }
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,9 @@ import org.jline.terminal.Terminal;
|
|||||||
import org.jline.terminal.TerminalBuilder;
|
import org.jline.terminal.TerminalBuilder;
|
||||||
import org.jline.utils.InfoCmp;
|
import org.jline.utils.InfoCmp;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ArrowMenu {
|
public class ArrowMenu {
|
||||||
@@ -15,19 +16,22 @@ public class ArrowMenu {
|
|||||||
private final List<String> options;
|
private final List<String> options;
|
||||||
private int selected = 0;
|
private int selected = 0;
|
||||||
private final Terminal terminal;
|
private final Terminal terminal;
|
||||||
|
private final InputStream rawInput;
|
||||||
|
|
||||||
private static final int VISIBLE_ITEMS = 7;
|
private static final int VISIBLE_ITEMS = 7;
|
||||||
|
|
||||||
public ArrowMenu(String title, List<String> options) throws IOException {
|
public ArrowMenu(String title, List<String> options) throws IOException {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
System.setProperty("jline.terminal", "unsupported");
|
boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows");
|
||||||
|
System.setProperty("jline.terminal", isWindows ? "win" : "unsupported");
|
||||||
this.terminal = TerminalBuilder.builder()
|
this.terminal = TerminalBuilder.builder()
|
||||||
.system(true)
|
.system(true)
|
||||||
.jna(false)
|
.jna(isWindows)
|
||||||
.jansi(true)
|
.jansi(true)
|
||||||
.encoding(StandardCharsets.UTF_8)
|
.encoding(StandardCharsets.UTF_8)
|
||||||
.build();
|
.build();
|
||||||
|
this.rawInput = terminal.input();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int show() throws IOException {
|
public int show() throws IOException {
|
||||||
@@ -38,39 +42,42 @@ public class ArrowMenu {
|
|||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
printPagedMenu();
|
printPagedMenu();
|
||||||
int key = terminal.reader().read();
|
int b = rawInput.read();
|
||||||
|
if (b == -1) continue;
|
||||||
|
|
||||||
if (key == 'w' || key == 'W' || key == 'ц' || key == 'Ц'
|
// w/W/k/K or ц (0xD1 0x86) = up
|
||||||
|| key == 'k' || key == 'K' || key == 'л' || key == 'Л') {
|
// s/S/j/J or ы (0xD1 0x8B) = down
|
||||||
|
if (b == 'w' || b == 'W' || b == 'k' || b == 'K') {
|
||||||
selected = (selected - 1 + options.size()) % options.size();
|
selected = (selected - 1 + options.size()) % options.size();
|
||||||
}
|
}
|
||||||
else if (key == 's' || key == 'S' || key == 'ы' || key == 'Ы'
|
else if (b == 's' || b == 'S' || b == 'j' || b == 'J') {
|
||||||
|| key == 'j' || key == 'J' || key == 'о' || key == 'О') {
|
|
||||||
selected = (selected + 1) % options.size();
|
selected = (selected + 1) % options.size();
|
||||||
}
|
}
|
||||||
else if (key == 13 || key == 10) {
|
// ESC sequences: arrows + cyrillic start byte
|
||||||
return selected;
|
else if (b == 0x1B) {
|
||||||
}
|
|
||||||
else if (key == 27) {
|
|
||||||
int next = nonBlockingRead();
|
int next = nonBlockingRead();
|
||||||
if (next == -1) {
|
if (next == -1) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (next == 91) {
|
if (next == 0x5B) { // '['
|
||||||
int arrow = nonBlockingRead();
|
int arrow = nonBlockingRead();
|
||||||
if (arrow == 65) {
|
if (arrow == 0x41) { // Up
|
||||||
selected = (selected - 1 + options.size()) % options.size();
|
selected = (selected - 1 + options.size()) % options.size();
|
||||||
} else if (arrow == 66) {
|
} else if (arrow == 0x42) { // Down
|
||||||
selected = (selected + 1) % options.size();
|
selected = (selected + 1) % options.size();
|
||||||
}
|
}
|
||||||
} else if (next == 79) {
|
}
|
||||||
int arrow = nonBlockingRead();
|
}
|
||||||
if (arrow == 68) {
|
else if (b == 0xD1) {
|
||||||
|
int second = nonBlockingRead();
|
||||||
|
if (second == 0x86) { // ц
|
||||||
selected = (selected - 1 + options.size()) % options.size();
|
selected = (selected - 1 + options.size()) % options.size();
|
||||||
} else if (arrow == 70) {
|
} else if (second == 0x8B) { // ы
|
||||||
return options.size() - 1;
|
selected = (selected + 1) % options.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (b == 13 || b == 10) {
|
||||||
|
return selected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -81,13 +88,12 @@ public class ArrowMenu {
|
|||||||
|
|
||||||
private int nonBlockingRead() throws IOException {
|
private int nonBlockingRead() throws IOException {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
while (System.currentTimeMillis() - startTime < 200) {
|
while (System.currentTimeMillis() - startTime < 100) {
|
||||||
int available = terminal.reader().available();
|
if (rawInput.available() > 0) {
|
||||||
if (available > 0) {
|
return rawInput.read();
|
||||||
return terminal.reader().read();
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Thread.sleep(5);
|
Thread.sleep(2);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -119,7 +125,7 @@ public class ArrowMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sb.append("\n")
|
sb.append("\n")
|
||||||
.append(ZAnsi.white("W/S (Ц/Ы) или ↑/↓ - перемещение | Enter - выбрать | Esc - назад"));
|
.append(ZAnsi.white("W/S or \u2191/\u2193 - navigate | Enter - select | Esc - back"));
|
||||||
|
|
||||||
System.out.print(sb);
|
System.out.print(sb);
|
||||||
System.out.flush();
|
System.out.flush();
|
||||||
|
|||||||
+145
-42
@@ -15,6 +15,7 @@ import me.sashegdev.zernmc.launcher.minecraft.Instance;
|
|||||||
import me.sashegdev.zernmc.launcher.minecraft.InstanceManager;
|
import me.sashegdev.zernmc.launcher.minecraft.InstanceManager;
|
||||||
import me.sashegdev.zernmc.launcher.minecraft.MinecraftLib;
|
import me.sashegdev.zernmc.launcher.minecraft.MinecraftLib;
|
||||||
import me.sashegdev.zernmc.launcher.minecraft.launch.LaunchCommandBuilder;
|
import me.sashegdev.zernmc.launcher.minecraft.launch.LaunchCommandBuilder;
|
||||||
|
import me.sashegdev.zernmc.launcher.utils.Config;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -49,6 +50,7 @@ public class JFXLauncher extends Application {
|
|||||||
private static StringBuilder launcherLogBuffer = new StringBuilder();
|
private static StringBuilder launcherLogBuffer = new StringBuilder();
|
||||||
private static StringBuilder gameLogBuffer = new StringBuilder();
|
private static StringBuilder gameLogBuffer = new StringBuilder();
|
||||||
private static Path gameLogFile;
|
private static Path gameLogFile;
|
||||||
|
private static Path launcherLogFile;
|
||||||
private Stage mainStage;
|
private Stage mainStage;
|
||||||
|
|
||||||
private static volatile String installProgressLabel = "";
|
private static volatile String installProgressLabel = "";
|
||||||
@@ -103,6 +105,7 @@ public class JFXLauncher extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void appendGameLog(String log) {
|
public static void appendGameLog(String log) {
|
||||||
|
System.out.println("[GAME] " + log);
|
||||||
synchronized (gameLogBuffer) {
|
synchronized (gameLogBuffer) {
|
||||||
gameLogBuffer.append(log).append("\n");
|
gameLogBuffer.append(log).append("\n");
|
||||||
|
|
||||||
@@ -167,15 +170,15 @@ public class JFXLauncher extends Application {
|
|||||||
|
|
||||||
String serverVersion = getServerVersion();
|
String serverVersion = getServerVersion();
|
||||||
if (serverVersion != null && !serverVersion.isEmpty()) {
|
if (serverVersion != null && !serverVersion.isEmpty()) {
|
||||||
System.out.println("[JFX] Загрузка assets через мета для версии " + serverVersion);
|
System.out.println("[JFX] Loading assets via meta for version " + serverVersion);
|
||||||
if (downloadAssetsFromMeta(serverVersion)) {
|
if (downloadAssetsFromMeta(serverVersion)) {
|
||||||
System.out.println("[JFX] Assets загружены через мета");
|
System.out.println("[JFX] Assets loaded via meta");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
System.out.println("[JFX] Мета недоступна, использую fallback");
|
System.out.println("[JFX] Meta unavailable, using fallback");
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("[JFX] Извлечение assets из JAR...");
|
System.out.println("[JFX] Extracting assets from JAR...");
|
||||||
Path jarPath = Paths.get(JFXLauncher.class.getProtectionDomain().getCodeSource().getLocation().toURI());
|
Path jarPath = Paths.get(JFXLauncher.class.getProtectionDomain().getCodeSource().getLocation().toURI());
|
||||||
if (Files.exists(jarPath) && jarPath.toString().endsWith(".jar")) {
|
if (Files.exists(jarPath) && jarPath.toString().endsWith(".jar")) {
|
||||||
try (JarFile jar = new JarFile(jarPath.toFile())) {
|
try (JarFile jar = new JarFile(jarPath.toFile())) {
|
||||||
@@ -195,10 +198,10 @@ public class JFXLauncher extends Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
System.out.println("[JFX] Assets извлечены из JAR");
|
System.out.println("[JFX] Assets extracted from JAR");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("[JFX] Ошибка извлечения assets: " + e.getMessage());
|
System.out.println("[JFX] Error extracting assets: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +271,7 @@ public class JFXLauncher extends Application {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("[JFX] Ошибка загрузки через мета: " + e.getMessage());
|
System.out.println("[JFX] Error loading via meta: " + e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,8 +281,15 @@ public class JFXLauncher extends Application {
|
|||||||
this.mainStage = stage;
|
this.mainStage = stage;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Initialize launcher log file
|
||||||
|
Path logsDir = Paths.get("logs");
|
||||||
|
Files.createDirectories(logsDir);
|
||||||
|
launcherLogFile = logsDir.resolve("launcher.log");
|
||||||
|
Files.writeString(launcherLogFile, "=== Launcher Log " + LocalDateTime.now() + " ===\n",
|
||||||
|
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
|
|
||||||
extractAssets();
|
extractAssets();
|
||||||
log("Запуск JFX UI...");
|
log("Starting JFX UI...");
|
||||||
startServer();
|
startServer();
|
||||||
|
|
||||||
WebView webView = new WebView();
|
WebView webView = new WebView();
|
||||||
@@ -289,7 +299,7 @@ public class JFXLauncher extends Application {
|
|||||||
engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
|
engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
|
||||||
log("[UI] Load state: " + oldState + " -> " + newState);
|
log("[UI] Load state: " + oldState + " -> " + newState);
|
||||||
if (newState == Worker.State.SUCCEEDED) {
|
if (newState == Worker.State.SUCCEEDED) {
|
||||||
log("Страница загружена");
|
log("Page loaded");
|
||||||
} else if (newState == Worker.State.FAILED) {
|
} else if (newState == Worker.State.FAILED) {
|
||||||
log("[UI] Load FAILED: " + engine.getLoadWorker().getException());
|
log("[UI] Load FAILED: " + engine.getLoadWorker().getException());
|
||||||
}
|
}
|
||||||
@@ -308,27 +318,17 @@ public class JFXLauncher extends Application {
|
|||||||
stage.setScene(new Scene(webView));
|
stage.setScene(new Scene(webView));
|
||||||
stage.show();
|
stage.show();
|
||||||
|
|
||||||
log("Окно отображено");
|
log("Window displayed");
|
||||||
|
|
||||||
stage.setOnCloseRequest(e -> {
|
stage.setOnCloseRequest(e -> {
|
||||||
log("Закрытие...");
|
log("Closing...");
|
||||||
LaunchService.killAllProcesses();
|
LaunchService.killAllProcesses();
|
||||||
if (server != null) server.stop(1);
|
if (server != null) server.stop(0);
|
||||||
|
|
||||||
try {
|
|
||||||
java.net.HttpURLConnection conn = (java.net.HttpURLConnection)
|
|
||||||
new java.net.URL("http://localhost:" + PORT + "/api/exit-parent").openConnection();
|
|
||||||
conn.setRequestMethod("POST");
|
|
||||||
conn.setConnectTimeout(1000);
|
|
||||||
conn.getResponseCode();
|
|
||||||
conn.disconnect();
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
|
|
||||||
Platform.exit();
|
Platform.exit();
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log("Ошибка: " + e.getMessage());
|
log("Error: " + e.getMessage());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -351,6 +351,9 @@ public class JFXLauncher extends Application {
|
|||||||
server.createContext("/api/mc-versions", this::handleMCVersions);
|
server.createContext("/api/mc-versions", this::handleMCVersions);
|
||||||
server.createContext("/api/loader-versions", this::handleLoaderVersions);
|
server.createContext("/api/loader-versions", this::handleLoaderVersions);
|
||||||
server.createContext("/api/packs", this::handlePacks);
|
server.createContext("/api/packs", this::handlePacks);
|
||||||
|
server.createContext("/api/settings", this::handleSettings);
|
||||||
|
server.createContext("/api/activate-pass", this::handleActivatePass);
|
||||||
|
server.createContext("/api/register", this::handleRegister);
|
||||||
server.createContext("/api/shutdown", this::handleShutdown);
|
server.createContext("/api/shutdown", this::handleShutdown);
|
||||||
server.createContext("/api/exit", this::handleExit);
|
server.createContext("/api/exit", this::handleExit);
|
||||||
server.createContext("/api/exit-parent", this::handleExitParent);
|
server.createContext("/api/exit-parent", this::handleExitParent);
|
||||||
@@ -359,7 +362,7 @@ public class JFXLauncher extends Application {
|
|||||||
server.setExecutor(Executors.newCachedThreadPool());
|
server.setExecutor(Executors.newCachedThreadPool());
|
||||||
server.start();
|
server.start();
|
||||||
|
|
||||||
log("HTTP сервер на порту " + PORT);
|
log("HTTP server on port " + PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopServer() {
|
private void stopServer() {
|
||||||
@@ -369,7 +372,7 @@ public class JFXLauncher extends Application {
|
|||||||
private void handleLogin(HttpExchange exchange) {
|
private void handleLogin(HttpExchange exchange) {
|
||||||
try {
|
try {
|
||||||
if (!"POST".equals(exchange.getRequestMethod())) {
|
if (!"POST".equals(exchange.getRequestMethod())) {
|
||||||
sendJson(exchange, Map.of("success", false, "error", "Метод не поддерживается"));
|
sendJson(exchange, Map.of("success", false, "error", "Method not supported"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +386,7 @@ public class JFXLauncher extends Application {
|
|||||||
data.put("username", result.getData().getUsername());
|
data.put("username", result.getData().getUsername());
|
||||||
data.put("token", result.getData().getToken());
|
data.put("token", result.getData().getToken());
|
||||||
sendJson(exchange, Map.of("success", true, "data", data));
|
sendJson(exchange, Map.of("success", true, "data", data));
|
||||||
log("Вход: " + username);
|
log("Login: " + username);
|
||||||
} else {
|
} else {
|
||||||
sendJson(exchange, Map.of("success", false, "error", result.getError()));
|
sendJson(exchange, Map.of("success", false, "error", result.getError()));
|
||||||
}
|
}
|
||||||
@@ -395,7 +398,7 @@ public class JFXLauncher extends Application {
|
|||||||
private void handleAccount(HttpExchange exchange) {
|
private void handleAccount(HttpExchange exchange) {
|
||||||
try {
|
try {
|
||||||
if (!api.isLoggedIn()) {
|
if (!api.isLoggedIn()) {
|
||||||
sendJson(exchange, Map.of("success", false, "error", "Не авторизован"));
|
sendJson(exchange, Map.of("success", false, "error", "Not authenticated"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Map<String, Object> data = new HashMap<>();
|
Map<String, Object> data = new HashMap<>();
|
||||||
@@ -418,7 +421,7 @@ public class JFXLauncher extends Application {
|
|||||||
data.put("role", AuthManager.getRole());
|
data.put("role", AuthManager.getRole());
|
||||||
data.put("roleName", AuthManager.getRoleName());
|
data.put("roleName", AuthManager.getRoleName());
|
||||||
sendJson(exchange, Map.of("success", true, "data", data, "autoLogin", true));
|
sendJson(exchange, Map.of("success", true, "data", data, "autoLogin", true));
|
||||||
log("Автологин выполнен: " + AuthManager.getUsername());
|
log("Auto-login performed: " + AuthManager.getUsername());
|
||||||
} else {
|
} else {
|
||||||
sendJson(exchange, Map.of("success", false, "autoLogin", false));
|
sendJson(exchange, Map.of("success", false, "autoLogin", false));
|
||||||
}
|
}
|
||||||
@@ -446,20 +449,30 @@ public class JFXLauncher extends Application {
|
|||||||
private void handleLaunch(HttpExchange exchange) {
|
private void handleLaunch(HttpExchange exchange) {
|
||||||
try {
|
try {
|
||||||
if (!api.isLoggedIn()) {
|
if (!api.isLoggedIn()) {
|
||||||
sendJson(exchange, Map.of("success", false, "error", "Не авторизован"));
|
sendJson(exchange, Map.of("success", false, "error", "Not authenticated"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> body = parseJson(exchange.getRequestBody());
|
Map<String, String> body = parseJson(exchange.getRequestBody());
|
||||||
String name = body.get("name");
|
String name = body.get("name");
|
||||||
|
|
||||||
|
Instance instance = InstanceManager.getInstance(name);
|
||||||
|
if (instance == null) {
|
||||||
|
sendJson(exchange, Map.of("success", false, "error", "Pack not found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (instance.isServerPack() && !AuthManager.hasActivePass()) {
|
||||||
|
sendJson(exchange, Map.of("success", false, "error", "Server pack requires an active pass"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var result = api.launch(name);
|
var result = api.launch(name);
|
||||||
if (result.isSuccess()) {
|
if (result.isSuccess()) {
|
||||||
Map<String, Object> data = new HashMap<>();
|
Map<String, Object> data = new HashMap<>();
|
||||||
data.put("pid", result.getData().getPid());
|
data.put("pid", result.getData().getPid());
|
||||||
data.put("status", result.getData().getStatus());
|
data.put("status", result.getData().getStatus());
|
||||||
sendJson(exchange, Map.of("success", true, "data", data));
|
sendJson(exchange, Map.of("success", true, "data", data));
|
||||||
log("Запущено: " + name);
|
log("Launched: " + name);
|
||||||
} else {
|
} else {
|
||||||
sendJson(exchange, Map.of("success", false, "error", result.getError()));
|
sendJson(exchange, Map.of("success", false, "error", result.getError()));
|
||||||
}
|
}
|
||||||
@@ -470,13 +483,13 @@ public class JFXLauncher extends Application {
|
|||||||
|
|
||||||
private void handleInstall(HttpExchange exchange) {
|
private void handleInstall(HttpExchange exchange) {
|
||||||
if (installInProgress) {
|
if (installInProgress) {
|
||||||
sendJson(exchange, Map.of("success", false, "error", "Установка уже выполняется"));
|
sendJson(exchange, Map.of("success", false, "error", "Installation already in progress"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!api.isLoggedIn()) {
|
if (!api.isLoggedIn()) {
|
||||||
sendJson(exchange, Map.of("success", false, "error", "Не авторизован"));
|
sendJson(exchange, Map.of("success", false, "error", "Not authenticated"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,7 +499,7 @@ public class JFXLauncher extends Application {
|
|||||||
String loader = body.get("loader");
|
String loader = body.get("loader");
|
||||||
String loaderVersion = body.get("loaderVersion");
|
String loaderVersion = body.get("loaderVersion");
|
||||||
|
|
||||||
log("Установка: " + name + " " + version + " " + loader + (loaderVersion != null ? " " + loaderVersion : ""));
|
log("Install: " + name + " " + version + " " + loader + (loaderVersion != null ? " " + loaderVersion : ""));
|
||||||
|
|
||||||
var createResult = api.instances().createInstance(name);
|
var createResult = api.instances().createInstance(name);
|
||||||
if (!createResult.isSuccess()) {
|
if (!createResult.isSuccess()) {
|
||||||
@@ -502,10 +515,10 @@ public class JFXLauncher extends Application {
|
|||||||
instance.setLoaderVersion(loaderVersion);
|
instance.setLoaderVersion(loaderVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendJson(exchange, Map.of("success", true, "message", "Установка началась"));
|
sendJson(exchange, Map.of("success", true, "message", "Installation started"));
|
||||||
|
|
||||||
setInstallInProgress(true);
|
setInstallInProgress(true);
|
||||||
setInstallProgress("Подготовка...", 0, 100);
|
setInstallProgress("Preparing...", 0, 100);
|
||||||
|
|
||||||
Thread installThread = new Thread(() -> {
|
Thread installThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -520,12 +533,12 @@ public class JFXLauncher extends Application {
|
|||||||
|
|
||||||
setInstallInProgress(false);
|
setInstallInProgress(false);
|
||||||
if (success) {
|
if (success) {
|
||||||
log("Установлено: " + name);
|
log("Installed: " + name);
|
||||||
} else {
|
} else {
|
||||||
log("Ошибка установки: " + name);
|
log("Install error: " + name);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log("Ошибка установки: " + e.getMessage());
|
log("Install error: " + e.getMessage());
|
||||||
setInstallInProgress(false);
|
setInstallInProgress(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -535,7 +548,7 @@ public class JFXLauncher extends Application {
|
|||||||
sendJson(exchange, Map.of("success", false, "error", "Instance not found"));
|
sendJson(exchange, Map.of("success", false, "error", "Instance not found"));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log("Ошибка установки: " + e.getMessage());
|
log("Install error: " + e.getMessage());
|
||||||
setInstallInProgress(false);
|
setInstallInProgress(false);
|
||||||
sendJson(exchange, Map.of("success", false, "error", e.getMessage()));
|
sendJson(exchange, Map.of("success", false, "error", e.getMessage()));
|
||||||
}
|
}
|
||||||
@@ -684,6 +697,90 @@ public class JFXLauncher extends Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleActivatePass(HttpExchange exchange) {
|
||||||
|
try {
|
||||||
|
if (!api.isLoggedIn()) {
|
||||||
|
sendJson(exchange, Map.of("success", false, "error", "Not authenticated"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, String> body = parseJson(exchange.getRequestBody());
|
||||||
|
String code = body.get("code");
|
||||||
|
if (code == null || code.isEmpty()) {
|
||||||
|
sendJson(exchange, Map.of("success", false, "error", "Enter pass code"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var result = api.activatePass(code);
|
||||||
|
if (result.isSuccess()) {
|
||||||
|
sendJson(exchange, Map.of("success", true, "message", "Pass activated!"));
|
||||||
|
log("Pass activated");
|
||||||
|
} else {
|
||||||
|
sendJson(exchange, Map.of("success", false, "error", result.getError()));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
sendJson(exchange, Map.of("success", false, "error", e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleRegister(HttpExchange exchange) {
|
||||||
|
try {
|
||||||
|
Map<String, String> body = parseJson(exchange.getRequestBody());
|
||||||
|
String username = body.get("username");
|
||||||
|
String password = body.get("password");
|
||||||
|
if (username == null || password == null || username.isEmpty() || password.isEmpty()) {
|
||||||
|
sendJson(exchange, Map.of("success", false, "error", "Fill all fields"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var result = api.register(username, password);
|
||||||
|
if (result.isSuccess()) {
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("username", result.getData().getUsername());
|
||||||
|
data.put("token", result.getData().getToken());
|
||||||
|
sendJson(exchange, Map.of("success", true, "data", data));
|
||||||
|
log("Registered: " + username);
|
||||||
|
} else {
|
||||||
|
sendJson(exchange, Map.of("success", false, "error", result.getError()));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
sendJson(exchange, Map.of("success", false, "error", e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSettings(HttpExchange exchange) {
|
||||||
|
try {
|
||||||
|
if ("POST".equals(exchange.getRequestMethod())) {
|
||||||
|
Map<String, String> body = parseJson(exchange.getRequestBody());
|
||||||
|
if (body.containsKey("maxMemory")) {
|
||||||
|
Config.setMaxMemory(Integer.parseInt(body.get("maxMemory")));
|
||||||
|
}
|
||||||
|
if (body.containsKey("windowWidth")) {
|
||||||
|
Config.setWindowWidth(Integer.parseInt(body.get("windowWidth")));
|
||||||
|
}
|
||||||
|
if (body.containsKey("windowHeight")) {
|
||||||
|
Config.setWindowHeight(Integer.parseInt(body.get("windowHeight")));
|
||||||
|
}
|
||||||
|
if (body.containsKey("extraJvmArgs")) {
|
||||||
|
Config.setExtraJvmArgs(body.get("extraJvmArgs"));
|
||||||
|
}
|
||||||
|
if (body.containsKey("javaPath")) {
|
||||||
|
Config.setJavaPath(body.get("javaPath"));
|
||||||
|
}
|
||||||
|
sendJson(exchange, Map.of("success", true));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("maxMemory", Config.getMaxMemory());
|
||||||
|
data.put("serverUrl", Config.getServerUrl());
|
||||||
|
data.put("instancesDir", Config.getInstancesDir().toString());
|
||||||
|
data.put("windowWidth", Config.getWindowWidth());
|
||||||
|
data.put("windowHeight", Config.getWindowHeight());
|
||||||
|
data.put("extraJvmArgs", Config.getExtraJvmArgs());
|
||||||
|
data.put("javaPath", Config.getJavaPath());
|
||||||
|
sendJson(exchange, Map.of("success", true, "data", data));
|
||||||
|
} catch (Exception e) {
|
||||||
|
sendJson(exchange, Map.of("success", false, "error", e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, String> parseQuery(String query) {
|
private Map<String, String> parseQuery(String query) {
|
||||||
Map<String, String> params = new HashMap<>();
|
Map<String, String> params = new HashMap<>();
|
||||||
if (query != null && !query.isEmpty()) {
|
if (query != null && !query.isEmpty()) {
|
||||||
@@ -700,13 +797,13 @@ public class JFXLauncher extends Application {
|
|||||||
private void handleShutdown(HttpExchange exchange) {
|
private void handleShutdown(HttpExchange exchange) {
|
||||||
log("Shutdown request received...");
|
log("Shutdown request received...");
|
||||||
LaunchService.killAllProcesses();
|
LaunchService.killAllProcesses();
|
||||||
if (server != null) server.stop(1);
|
if (server != null) server.stop(0);
|
||||||
Platform.exit();
|
Platform.exit();
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleExit(HttpExchange exchange) {
|
private void handleExit(HttpExchange exchange) {
|
||||||
log("Выход...");
|
log("Exiting...");
|
||||||
LaunchService.killAllProcesses();
|
LaunchService.killAllProcesses();
|
||||||
if (mainStage != null) mainStage.close();
|
if (mainStage != null) mainStage.close();
|
||||||
Platform.exit();
|
Platform.exit();
|
||||||
@@ -714,7 +811,7 @@ public class JFXLauncher extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleExitParent(HttpExchange exchange) {
|
private void handleExitParent(HttpExchange exchange) {
|
||||||
log("Завершение родительского процесса...");
|
log("Terminating parent process...");
|
||||||
LaunchService.killAllProcesses();
|
LaunchService.killAllProcesses();
|
||||||
if (mainStage != null) mainStage.close();
|
if (mainStage != null) mainStage.close();
|
||||||
Platform.exit();
|
Platform.exit();
|
||||||
@@ -782,6 +879,12 @@ public class JFXLauncher extends Application {
|
|||||||
launcherLogBuffer.append(entry);
|
launcherLogBuffer.append(entry);
|
||||||
}
|
}
|
||||||
System.out.println("[JFX] " + msg);
|
System.out.println("[JFX] " + msg);
|
||||||
|
if (launcherLogFile != null) {
|
||||||
|
try {
|
||||||
|
Files.writeString(launcherLogFile, entry,
|
||||||
|
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
for (LogConsumer consumer : logConsumers) {
|
for (LogConsumer consumer : logConsumers) {
|
||||||
try { consumer.onLog(entry); } catch (Exception ignored) {}
|
try { consumer.onLog(entry); } catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,13 @@ public class Config {
|
|||||||
|
|
||||||
private static final Properties props = new Properties();
|
private static final Properties props = new Properties();
|
||||||
|
|
||||||
// Настройки
|
private static int maxMemory = 4096;
|
||||||
private static int maxMemory = 4096; // будет перезаписано умной логикой
|
|
||||||
private static String serverUrl = "http://87.120.187.36:1582";
|
private static String serverUrl = "http://87.120.187.36:1582";
|
||||||
private static String lastUsername = "Player";
|
private static String lastUsername = "Player";
|
||||||
|
private static int windowWidth = 1280;
|
||||||
|
private static int windowHeight = 720;
|
||||||
|
private static String extraJvmArgs = "";
|
||||||
|
private static String javaPath = "java";
|
||||||
|
|
||||||
static {
|
static {
|
||||||
load();
|
load();
|
||||||
@@ -36,9 +39,13 @@ public class Config {
|
|||||||
maxMemory = Integer.parseInt(props.getProperty("maxMemory", "4096"));
|
maxMemory = Integer.parseInt(props.getProperty("maxMemory", "4096"));
|
||||||
serverUrl = props.getProperty("serverUrl", serverUrl);
|
serverUrl = props.getProperty("serverUrl", serverUrl);
|
||||||
lastUsername = props.getProperty("lastUsername", lastUsername);
|
lastUsername = props.getProperty("lastUsername", lastUsername);
|
||||||
|
windowWidth = Integer.parseInt(props.getProperty("windowWidth", "1280"));
|
||||||
|
windowHeight = Integer.parseInt(props.getProperty("windowHeight", "720"));
|
||||||
|
extraJvmArgs = props.getProperty("extraJvmArgs", "");
|
||||||
|
javaPath = props.getProperty("javaPath", "java");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println(ZAnsi.brightRed("Не удалось загрузить конфиг: ") + e.getMessage());
|
System.err.println(ZAnsi.brightRed("Failed to load config: ") + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,40 +54,34 @@ public class Config {
|
|||||||
props.setProperty("maxMemory", String.valueOf(maxMemory));
|
props.setProperty("maxMemory", String.valueOf(maxMemory));
|
||||||
props.setProperty("serverUrl", serverUrl);
|
props.setProperty("serverUrl", serverUrl);
|
||||||
props.setProperty("lastUsername", lastUsername);
|
props.setProperty("lastUsername", lastUsername);
|
||||||
|
props.setProperty("windowWidth", String.valueOf(windowWidth));
|
||||||
|
props.setProperty("windowHeight", String.valueOf(windowHeight));
|
||||||
|
props.setProperty("extraJvmArgs", extraJvmArgs);
|
||||||
|
props.setProperty("javaPath", javaPath);
|
||||||
|
|
||||||
try (var os = Files.newOutputStream(CONFIG_FILE)) {
|
try (var os = Files.newOutputStream(CONFIG_FILE)) {
|
||||||
props.store(os, "ZernMC Launcher Configuration");
|
props.store(os, "ZernMC Launcher Configuration");
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.err.println(ZAnsi.brightRed("Не удалось сохранить конфиг: ") + e.getMessage());
|
System.err.println(ZAnsi.brightRed("Failed to save config: ") + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Умная рекомендация RAM:
|
|
||||||
* - минимум 1.5 GB
|
|
||||||
* - рекомендуется totalRAM - 30%
|
|
||||||
* - максимум 70% от доступной RAM
|
|
||||||
*/
|
|
||||||
private static void applySmartRamRecommendation() {
|
private static void applySmartRamRecommendation() {
|
||||||
long totalRamMB = Runtime.getRuntime().maxMemory() / (1024 * 1024); // в MB
|
long totalRamMB = Runtime.getRuntime().maxMemory() / (1024 * 1024);
|
||||||
|
|
||||||
// Рекомендуемое значение = total - 30%
|
long recommended = (long) (totalRamMB * 0.70);
|
||||||
long recommended = (long) (totalRamMB * 0.70); // 70% от доступной
|
|
||||||
|
|
||||||
// Ограничения
|
recommended = Math.max(1536, recommended);
|
||||||
recommended = Math.max(1536, recommended); // минимум 1.5 GB
|
recommended = Math.min(recommended, totalRamMB - 1024);
|
||||||
recommended = Math.min(recommended, totalRamMB - 1024); // оставляем минимум 1 GB системе
|
|
||||||
|
|
||||||
// Если текущее значение сильно отличается от рекомендуемого — корректируем
|
if (Math.abs(maxMemory - recommended) > 1024) {
|
||||||
if (Math.abs(maxMemory - recommended) > 1024) { // разница больше 1 GB
|
|
||||||
maxMemory = (int) recommended;
|
maxMemory = (int) recommended;
|
||||||
save(); // сохраняем умную рекомендацию
|
save();
|
||||||
System.out.println(ZAnsi.cyan("Автоматически рекомендовано RAM: " + maxMemory + " MB"));
|
System.out.println(ZAnsi.cyan("Auto-recommended RAM: " + maxMemory + " MB"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters & Setters
|
|
||||||
public static int getMaxMemory() {
|
public static int getMaxMemory() {
|
||||||
return maxMemory;
|
return maxMemory;
|
||||||
}
|
}
|
||||||
@@ -94,7 +95,6 @@ public class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void setMaxMemory(int memory) {
|
public static void setMaxMemory(int memory) {
|
||||||
// Защита от слишком маленьких/больших значений
|
|
||||||
if (memory < 1024) memory = 1536;
|
if (memory < 1024) memory = 1536;
|
||||||
if (memory > 32768) memory = 32768;
|
if (memory > 32768) memory = 32768;
|
||||||
|
|
||||||
@@ -127,11 +127,44 @@ public class Config {
|
|||||||
return CONFIG_DIR;
|
return CONFIG_DIR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static int getWindowWidth() {
|
||||||
* Полезная информация для пользователя
|
return windowWidth;
|
||||||
*/
|
}
|
||||||
|
|
||||||
|
public static void setWindowWidth(int width) {
|
||||||
|
windowWidth = Math.max(640, width);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getWindowHeight() {
|
||||||
|
return windowHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setWindowHeight(int height) {
|
||||||
|
windowHeight = Math.max(480, height);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getExtraJvmArgs() {
|
||||||
|
return extraJvmArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setExtraJvmArgs(String args) {
|
||||||
|
extraJvmArgs = args != null ? args : "";
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getJavaPath() {
|
||||||
|
return javaPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setJavaPath(String path) {
|
||||||
|
javaPath = path != null && !path.isEmpty() ? path : "java";
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
public static String getRamInfo() {
|
public static String getRamInfo() {
|
||||||
long totalMB = Runtime.getRuntime().maxMemory() / (1024 * 1024);
|
long totalMB = Runtime.getRuntime().maxMemory() / (1024 * 1024);
|
||||||
return "Доступно RAM: " + totalMB + " MB | Рекомендуется: " + maxMemory + " MB";
|
return "Available RAM: " + totalMB + " MB | Recommended: " + maxMemory + " MB";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,10 +10,9 @@ public class ConsoleUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void pause() {
|
public static void pause() {
|
||||||
System.out.print(ZAnsi.white("\nНажмите Enter для продолжения..."));
|
System.out.print(ZAnsi.white("\nPress Enter to continue..."));
|
||||||
try {
|
try {
|
||||||
System.in.read();
|
System.in.read();
|
||||||
// Очищаем буфер ввода
|
|
||||||
while (System.in.available() > 0) {
|
while (System.in.available() > 0) {
|
||||||
System.in.read();
|
System.in.read();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
/**
|
|
||||||
* Улучшенный Input с поддержкой кириллицы и confirm через ArrowMenu
|
|
||||||
*/
|
|
||||||
public class Input {
|
public class Input {
|
||||||
|
|
||||||
private static final Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8);
|
private static final Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8);
|
||||||
@@ -30,7 +27,7 @@ public class Input {
|
|||||||
System.out.print(prompt);
|
System.out.print(prompt);
|
||||||
return Integer.parseInt(scanner.nextLine().trim());
|
return Integer.parseInt(scanner.nextLine().trim());
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
System.out.println(ZAnsi.brightRed("Некорректное число. Попробуйте ещё раз."));
|
System.out.println(ZAnsi.brightRed("Invalid number. Try again."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,20 +38,16 @@ public class Input {
|
|||||||
if (value >= min && value <= max) {
|
if (value >= min && value <= max) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
System.out.println(ZAnsi.brightRed("Значение должно быть от " + min + " до " + max + "."));
|
System.out.println(ZAnsi.brightRed("Value must be between " + min + " and " + max + "."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Новый confirm через ArrowMenu
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public static boolean confirm(String question) throws IOException {
|
public static boolean confirm(String question) throws IOException {
|
||||||
ConsoleUtils.clearScreen();
|
ConsoleUtils.clearScreen();
|
||||||
|
|
||||||
List<String> options = List.of(
|
List<String> options = List.of(
|
||||||
"Да",
|
"Yes",
|
||||||
"Нет"
|
"No"
|
||||||
);
|
);
|
||||||
|
|
||||||
ArrowMenu menu = new ArrowMenu(question, options);
|
ArrowMenu menu = new ArrowMenu(question, options);
|
||||||
@@ -63,28 +56,17 @@ public class Input {
|
|||||||
return choice == 0;
|
return choice == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Альтернативный confirm без очистки экрана
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public static boolean confirmInline(String question) throws IOException {
|
public static boolean confirmInline(String question) throws IOException {
|
||||||
List<String> options = List.of("Да", "Нет");
|
List<String> options = List.of("Yes", "No");
|
||||||
ArrowMenu menu = new ArrowMenu(question, options);
|
ArrowMenu menu = new ArrowMenu(question, options);
|
||||||
int choice = menu.show();
|
int choice = menu.show();
|
||||||
return choice == 0;
|
return choice == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Закрытие сканнера (вызывать при выходе из программы, если нужно)
|
|
||||||
*/
|
|
||||||
public static void close() {
|
public static void close() {
|
||||||
scanner.close();
|
scanner.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Очищает буфер ввода от оставшихся символов
|
|
||||||
*/
|
|
||||||
public static void flushInput() {
|
public static void flushInput() {
|
||||||
try {
|
try {
|
||||||
while (System.in.available() > 0) {
|
while (System.in.available() > 0) {
|
||||||
|
|||||||
@@ -10,9 +10,6 @@ public class ProgressBar {
|
|||||||
private static String currentLabel = "";
|
private static String currentLabel = "";
|
||||||
private static long currentTotal = 0;
|
private static long currentTotal = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* Прогресс по количеству файлов (для библиотек и общего прогресса)
|
|
||||||
*/
|
|
||||||
public static void show(String label, long current, long total, String unit) {
|
public static void show(String label, long current, long total, String unit) {
|
||||||
currentLabel = label;
|
currentLabel = label;
|
||||||
currentTotal = total;
|
currentTotal = total;
|
||||||
@@ -39,9 +36,6 @@ public class ProgressBar {
|
|||||||
System.out.flush();
|
System.out.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Прогресс по байтам для одного файла (реальный прогресс)
|
|
||||||
*/
|
|
||||||
public static void showDownload(String label, long downloaded, long totalBytes) {
|
public static void showDownload(String label, long downloaded, long totalBytes) {
|
||||||
currentLabel = label;
|
currentLabel = label;
|
||||||
currentTotal = totalBytes;
|
currentTotal = totalBytes;
|
||||||
@@ -99,7 +93,7 @@ public class ProgressBar {
|
|||||||
setInProgress.invoke(null, false);
|
setInProgress.invoke(null, false);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
System.out.println("\r" + ZAnsi.brightGreen(message + " завершено ✓"));
|
System.out.println("\r" + ZAnsi.brightGreen(message + " done ✓"));
|
||||||
System.out.flush();
|
System.out.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,14 +29,9 @@ public class ZHttpClient {
|
|||||||
|
|
||||||
private static String BASE_URL = "http://87.120.187.36:1582";
|
private static String BASE_URL = "http://87.120.187.36:1582";
|
||||||
|
|
||||||
// Глобальный прокси режим (для обратной совместимости)
|
|
||||||
private static final AtomicBoolean useProxyMode = new AtomicBoolean(false);
|
private static final AtomicBoolean useProxyMode = new AtomicBoolean(false);
|
||||||
private static final AtomicBoolean proxyTested = new AtomicBoolean(false);
|
private static final AtomicBoolean proxyTested = new AtomicBoolean(false);
|
||||||
|
|
||||||
/**
|
|
||||||
* Переопределить URL сервера (для тестов).
|
|
||||||
* Внимание: не потокобезопасно, использовать только в тестах.
|
|
||||||
*/
|
|
||||||
public static void setBaseUrl(String url) {
|
public static void setBaseUrl(String url) {
|
||||||
BASE_URL = url;
|
BASE_URL = url;
|
||||||
}
|
}
|
||||||
@@ -45,7 +40,6 @@ public class ZHttpClient {
|
|||||||
return BASE_URL;
|
return BASE_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Умное проксирование по сервисам
|
|
||||||
public enum ServiceType {
|
public enum ServiceType {
|
||||||
ZERN_SERVER("http://87.120.187.36:1582", true),
|
ZERN_SERVER("http://87.120.187.36:1582", true),
|
||||||
FABRIC_META("https://meta.fabricmc.net", false),
|
FABRIC_META("https://meta.fabricmc.net", false),
|
||||||
@@ -69,17 +63,15 @@ public class ZHttpClient {
|
|||||||
public boolean isAlwaysDirect() { return alwaysDirect; }
|
public boolean isAlwaysDirect() { return alwaysDirect; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Статусы сервисов
|
|
||||||
private static final Map<ServiceType, Boolean> serviceProxyMode = new ConcurrentHashMap<>();
|
private static final Map<ServiceType, Boolean> serviceProxyMode = new ConcurrentHashMap<>();
|
||||||
private static final Map<ServiceType, Integer> serviceFailCount = new ConcurrentHashMap<>();
|
private static final Map<ServiceType, Integer> serviceFailCount = new ConcurrentHashMap<>();
|
||||||
private static final Map<ServiceType, Long> serviceLastCheckTime = new ConcurrentHashMap<>();
|
private static final Map<ServiceType, Long> serviceLastCheckTime = new ConcurrentHashMap<>();
|
||||||
private static final Map<ServiceType, Boolean> serviceHealthy = new ConcurrentHashMap<>();
|
private static final Map<ServiceType, Boolean> serviceHealthy = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private static final int MAX_FAILS_BEFORE_PROXY = 2;
|
private static final int MAX_FAILS_BEFORE_PROXY = 2;
|
||||||
private static final long HEALTH_CHECK_INTERVAL_MS = 60000; // 1 минута
|
private static final long HEALTH_CHECK_INTERVAL_MS = 60000;
|
||||||
private static final long CHECK_TIMEOUT_MS = 7000; // 7 секунд на проверку
|
private static final long CHECK_TIMEOUT_MS = 7000;
|
||||||
|
|
||||||
// Статистика
|
|
||||||
private static int directSuccessCount = 0;
|
private static int directSuccessCount = 0;
|
||||||
private static int proxySuccessCount = 0;
|
private static int proxySuccessCount = 0;
|
||||||
private static int directFailCount = 0;
|
private static int directFailCount = 0;
|
||||||
@@ -92,9 +84,6 @@ public class ZHttpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Вызывать один раз при запуске лаунчера
|
|
||||||
*/
|
|
||||||
public static void checkAllServicesOnStartup() {
|
public static void checkAllServicesOnStartup() {
|
||||||
checkAllServicesOnStartup(false);
|
checkAllServicesOnStartup(false);
|
||||||
}
|
}
|
||||||
@@ -121,16 +110,16 @@ public class ZHttpClient {
|
|||||||
if (verbose) {
|
if (verbose) {
|
||||||
System.out.println(isHealthy ?
|
System.out.println(isHealthy ?
|
||||||
ZAnsi.green(" " + service.name() + " - OK") :
|
ZAnsi.green(" " + service.name() + " - OK") :
|
||||||
ZAnsi.red(" " + service.name() + " - НЕ ДОСТУПЕН (критично!)"));
|
ZAnsi.red(" " + service.name() + " - NOT ACCESSIBLE (critical!)"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isHealthy) {
|
if (isHealthy) {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
System.out.println(ZAnsi.green(" " + service.name() + " - прямое подключение работает"));
|
System.out.println(ZAnsi.green(" " + service.name() + " - direct connection works"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
System.out.println(ZAnsi.yellow(" " + service.name() + " - НЕ ДОСТУПЕН, будет использован прокси"));
|
System.out.println(ZAnsi.yellow(" " + service.name() + " - NOT ACCESSIBLE, proxy will be used"));
|
||||||
}
|
}
|
||||||
serviceProxyMode.put(service, true);
|
serviceProxyMode.put(service, true);
|
||||||
serviceFailCount.put(service, MAX_FAILS_BEFORE_PROXY);
|
serviceFailCount.put(service, MAX_FAILS_BEFORE_PROXY);
|
||||||
@@ -140,7 +129,7 @@ public class ZHttpClient {
|
|||||||
|
|
||||||
if (!serviceHealthy.get(ServiceType.ZERN_SERVER)) {
|
if (!serviceHealthy.get(ServiceType.ZERN_SERVER)) {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
System.out.println(ZAnsi.brightRed("Критическая ошибка: Zern сервер недоступен!"));
|
System.out.println(ZAnsi.brightRed("Critical error: Zern server is unreachable!"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,22 +140,19 @@ public class ZHttpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Принудительная проверка Mojang-сервисов (рекомендуется вызывать перед установкой сборки)
|
|
||||||
*/
|
|
||||||
public static void forceCheckMojangServices() {
|
public static void forceCheckMojangServices() {
|
||||||
System.out.println(ZAnsi.cyan("Принудительная проверка Mojang сервисов..."));
|
System.out.println(ZAnsi.cyan("Forcing Mojang services check..."));
|
||||||
|
|
||||||
for (ServiceType service : List.of(ServiceType.MOJANG_META, ServiceType.MOJANG_RESOURCES)) {
|
for (ServiceType service : List.of(ServiceType.MOJANG_META, ServiceType.MOJANG_RESOURCES)) {
|
||||||
boolean healthy = checkServiceHealth(service);
|
boolean healthy = checkServiceHealth(service);
|
||||||
serviceHealthy.put(service, healthy);
|
serviceHealthy.put(service, healthy);
|
||||||
|
|
||||||
if (healthy) {
|
if (healthy) {
|
||||||
System.out.println(ZAnsi.green(" " + service.name() + " доступен напрямую"));
|
System.out.println(ZAnsi.green(" " + service.name() + " accessible directly"));
|
||||||
serviceProxyMode.put(service, false);
|
serviceProxyMode.put(service, false);
|
||||||
serviceFailCount.put(service, 0);
|
serviceFailCount.put(service, 0);
|
||||||
} else {
|
} else {
|
||||||
System.out.println(ZAnsi.yellow(" " + service.name() + " недоступен → прокси режим активирован"));
|
System.out.println(ZAnsi.yellow(" " + service.name() + " not accessible -> proxy mode activated"));
|
||||||
serviceProxyMode.put(service, true);
|
serviceProxyMode.put(service, true);
|
||||||
serviceFailCount.put(service, MAX_FAILS_BEFORE_PROXY);
|
serviceFailCount.put(service, MAX_FAILS_BEFORE_PROXY);
|
||||||
}
|
}
|
||||||
@@ -177,9 +163,6 @@ public class ZHttpClient {
|
|||||||
return checkDirectConnection(service.getBaseUrl());
|
return checkDirectConnection(service.getBaseUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Улучшенная проверка прямого подключения
|
|
||||||
*/
|
|
||||||
private static boolean checkDirectConnection(String baseUrl) {
|
private static boolean checkDirectConnection(String baseUrl) {
|
||||||
String testUrl = baseUrl;
|
String testUrl = baseUrl;
|
||||||
|
|
||||||
@@ -199,7 +182,7 @@ public class ZHttpClient {
|
|||||||
|
|
||||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
int code = response.statusCode();
|
int code = response.statusCode();
|
||||||
return code == 200 || code == 404; // 404 для ресурсов — нормально
|
return code == 200 || code == 404;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -230,7 +213,7 @@ public class ZHttpClient {
|
|||||||
if (isHealthy && serviceProxyMode.get(service)) {
|
if (isHealthy && serviceProxyMode.get(service)) {
|
||||||
serviceProxyMode.put(service, false);
|
serviceProxyMode.put(service, false);
|
||||||
serviceFailCount.put(service, 0);
|
serviceFailCount.put(service, 0);
|
||||||
System.out.println(ZAnsi.green("[NET] " + service.name() + " восстановлен, переключен на прямое подключение"));
|
System.out.println(ZAnsi.green("[NET] " + service.name() + " restored, switched to direct connection"));
|
||||||
} else if (!isHealthy && !serviceProxyMode.get(service)) {
|
} else if (!isHealthy && !serviceProxyMode.get(service)) {
|
||||||
int fails = serviceFailCount.getOrDefault(service, 0) + 1;
|
int fails = serviceFailCount.getOrDefault(service, 0) + 1;
|
||||||
serviceFailCount.put(service, fails);
|
serviceFailCount.put(service, fails);
|
||||||
@@ -238,7 +221,7 @@ public class ZHttpClient {
|
|||||||
|
|
||||||
if (fails >= MAX_FAILS_BEFORE_PROXY) {
|
if (fails >= MAX_FAILS_BEFORE_PROXY) {
|
||||||
serviceProxyMode.put(service, true);
|
serviceProxyMode.put(service, true);
|
||||||
System.out.println(ZAnsi.yellow("[NET] " + service.name() + " недоступен, включен прокси режим"));
|
System.out.println(ZAnsi.yellow("[NET] " + service.name() + " unavailable, proxy mode enabled"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,14 +272,11 @@ public class ZHttpClient {
|
|||||||
|
|
||||||
if (fails >= MAX_FAILS_BEFORE_PROXY && !serviceProxyMode.get(service)) {
|
if (fails >= MAX_FAILS_BEFORE_PROXY && !serviceProxyMode.get(service)) {
|
||||||
serviceProxyMode.put(service, true);
|
serviceProxyMode.put(service, true);
|
||||||
System.out.println(ZAnsi.yellow("[NET] " + service.name() + " заблокирован, переключаемся на прокси"));
|
System.out.println(ZAnsi.yellow("[NET] " + service.name() + " blocked, switching to proxy"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Универсальный GET с умным прокси + автоматическим fallback
|
|
||||||
*/
|
|
||||||
public static String getWithSmartProxy(String url) throws IOException, InterruptedException {
|
public static String getWithSmartProxy(String url) throws IOException, InterruptedException {
|
||||||
// Попытка прямого подключения
|
|
||||||
if (!shouldUseProxyForUrl(url)) {
|
if (!shouldUseProxyForUrl(url)) {
|
||||||
try {
|
try {
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
@@ -321,11 +301,9 @@ public class ZHttpClient {
|
|||||||
directFailCount++;
|
directFailCount++;
|
||||||
markServiceAsBlocked(url);
|
markServiceAsBlocked(url);
|
||||||
}
|
}
|
||||||
// Если ошибка соединения — пробуем через прокси
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Через прокси
|
|
||||||
try {
|
try {
|
||||||
String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);
|
String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);
|
||||||
String proxyUrl = BASE_URL + "/download?url=" + encodedUrl;
|
String proxyUrl = BASE_URL + "/download?url=" + encodedUrl;
|
||||||
@@ -347,13 +325,10 @@ public class ZHttpClient {
|
|||||||
return response.body();
|
return response.body();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IOException("Не удалось получить данные ни напрямую, ни через прокси: " + e.getMessage(), e);
|
throw new IOException("Failed to fetch data directly or via proxy: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Скачивание файла с умным прокси + fallback
|
|
||||||
*/
|
|
||||||
public static void downloadFileWithSmartProxy(String url, Path target) throws Exception {
|
public static void downloadFileWithSmartProxy(String url, Path target) throws Exception {
|
||||||
if (!shouldUseProxyForUrl(url)) {
|
if (!shouldUseProxyForUrl(url)) {
|
||||||
try {
|
try {
|
||||||
@@ -375,11 +350,9 @@ public class ZHttpClient {
|
|||||||
directFailCount++;
|
directFailCount++;
|
||||||
markServiceAsBlocked(url);
|
markServiceAsBlocked(url);
|
||||||
}
|
}
|
||||||
// fallback на прокси ниже
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Скачивание через прокси
|
|
||||||
String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);
|
String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);
|
||||||
String proxyUrl = BASE_URL + "/proxy/download?url=" + encodedUrl;
|
String proxyUrl = BASE_URL + "/proxy/download?url=" + encodedUrl;
|
||||||
|
|
||||||
@@ -399,8 +372,6 @@ public class ZHttpClient {
|
|||||||
proxySuccessCount++;
|
proxySuccessCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== СТАРЫЕ МЕТОДЫ (обновлённые) ======================
|
|
||||||
|
|
||||||
public static String get(String endpoint) throws IOException, InterruptedException {
|
public static String get(String endpoint) throws IOException, InterruptedException {
|
||||||
checkAllServicesOnStartup();
|
checkAllServicesOnStartup();
|
||||||
|
|
||||||
@@ -415,7 +386,6 @@ public class ZHttpClient {
|
|||||||
.header("User-Agent", "ZernMC-Launcher/1.0")
|
.header("User-Agent", "ZernMC-Launcher/1.0")
|
||||||
.GET();
|
.GET();
|
||||||
|
|
||||||
// ===== ДОБАВИТЬ ТОКЕН АВТОРИЗАЦИИ =====
|
|
||||||
String accessToken = AuthManager.getAccessToken();
|
String accessToken = AuthManager.getAccessToken();
|
||||||
if (accessToken != null && !accessToken.equals("0")) {
|
if (accessToken != null && !accessToken.equals("0")) {
|
||||||
requestBuilder.header("Authorization", "Bearer " + accessToken);
|
requestBuilder.header("Authorization", "Bearer " + accessToken);
|
||||||
@@ -442,7 +412,6 @@ public class ZHttpClient {
|
|||||||
.header("User-Agent", "ZernMC-Launcher/1.0")
|
.header("User-Agent", "ZernMC-Launcher/1.0")
|
||||||
.GET();
|
.GET();
|
||||||
|
|
||||||
// ===== ДОБАВИТЬ ТОКЕН АВТОРИЗАЦИИ =====
|
|
||||||
String accessToken = AuthManager.getAccessToken();
|
String accessToken = AuthManager.getAccessToken();
|
||||||
if (accessToken != null && !accessToken.equals("0")) {
|
if (accessToken != null && !accessToken.equals("0")) {
|
||||||
requestBuilder.header("Authorization", "Bearer " + accessToken);
|
requestBuilder.header("Authorization", "Bearer " + accessToken);
|
||||||
@@ -458,12 +427,10 @@ public class ZHttpClient {
|
|||||||
proxySuccessCount++;
|
proxySuccessCount++;
|
||||||
return response.body();
|
return response.body();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IOException("Ошибка прокси: " + e.getMessage(), e);
|
throw new IOException("Proxy error: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== МЕТОДЫ ДЛЯ EXTERNAL РЕСУРСОВ ======================
|
|
||||||
|
|
||||||
public static List<String> getFabricLoaderVersions() throws IOException, InterruptedException {
|
public static List<String> getFabricLoaderVersions() throws IOException, InterruptedException {
|
||||||
String url = "https://meta.fabricmc.net/v2/versions/loader";
|
String url = "https://meta.fabricmc.net/v2/versions/loader";
|
||||||
return parseFabricVersionsFromJson(getWithSmartProxy(url));
|
return parseFabricVersionsFromJson(getWithSmartProxy(url));
|
||||||
@@ -518,15 +485,13 @@ public class ZHttpClient {
|
|||||||
return versions;
|
return versions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== ВСПОМОГАТЕЛЬНЫЕ ======================
|
|
||||||
|
|
||||||
public static String getLauncherVersionInfo() throws IOException, InterruptedException {
|
public static String getLauncherVersionInfo() throws IOException, InterruptedException {
|
||||||
return get("/launcher/version");
|
return get("/launcher/version");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void forceProxyMode() {
|
public static void forceProxyMode() {
|
||||||
useProxyMode.set(true);
|
useProxyMode.set(true);
|
||||||
System.out.println(ZAnsi.yellow("Принудительно включен глобальный прокси режим"));
|
System.out.println(ZAnsi.yellow("Global proxy mode forced on"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void disableProxyMode() {
|
public static void disableProxyMode() {
|
||||||
@@ -537,7 +502,7 @@ public class ZHttpClient {
|
|||||||
serviceFailCount.put(type, 0);
|
serviceFailCount.put(type, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
System.out.println(ZAnsi.green("Режим прокси выключен"));
|
System.out.println(ZAnsi.green("Proxy mode disabled"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isProxyMode() {
|
public static boolean isProxyMode() {
|
||||||
@@ -545,16 +510,16 @@ public class ZHttpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void printStats() {
|
public static void printStats() {
|
||||||
System.out.println(ZAnsi.cyan("\n=== Статистика сети ==="));
|
System.out.println(ZAnsi.cyan("\n=== Network Stats ==="));
|
||||||
System.out.println(ZAnsi.white("Глобальный прокси: ") + (useProxyMode.get() ? "ВКЛ" : "ВЫКЛ"));
|
System.out.println(ZAnsi.white("Global proxy: ") + (useProxyMode.get() ? "ON" : "OFF"));
|
||||||
System.out.println(ZAnsi.white("Прямых успехов: ") + directSuccessCount);
|
System.out.println(ZAnsi.white("Direct successes: ") + directSuccessCount);
|
||||||
System.out.println(ZAnsi.white("Прямых неудач: ") + directFailCount);
|
System.out.println(ZAnsi.white("Direct failures: ") + directFailCount);
|
||||||
System.out.println(ZAnsi.white("Прокси успехов: ") + proxySuccessCount);
|
System.out.println(ZAnsi.white("Proxy successes: ") + proxySuccessCount);
|
||||||
|
|
||||||
System.out.println(ZAnsi.cyan("\nСтатус сервисов:"));
|
System.out.println(ZAnsi.cyan("\nService status:"));
|
||||||
for (ServiceType type : ServiceType.values()) {
|
for (ServiceType type : ServiceType.values()) {
|
||||||
if (type.isAlwaysDirect()) continue;
|
if (type.isAlwaysDirect()) continue;
|
||||||
String status = serviceProxyMode.get(type) ? ZAnsi.red("ПРОКСИ") : ZAnsi.green("ПРЯМО");
|
String status = serviceProxyMode.get(type) ? ZAnsi.red("PROXY") : ZAnsi.green("DIRECT");
|
||||||
String health = serviceHealthy.get(type) ? ZAnsi.green("[+]") : ZAnsi.red("[-]");
|
String health = serviceHealthy.get(type) ? ZAnsi.green("[+]") : ZAnsi.red("[-]");
|
||||||
System.out.println(ZAnsi.white(" " + type.name() + ": ") + status + " " + health);
|
System.out.println(ZAnsi.white(" " + type.name() + ": ") + status + " " + health);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="ru">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
@@ -7,220 +7,360 @@
|
|||||||
<link rel="stylesheet" href="style.css">
|
<link rel="stylesheet" href="style.css">
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<canvas id="grid-canvas"></canvas>
|
<canvas id="bg-canvas"></canvas>
|
||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<!-- Login Screen -->
|
<!-- Login Screen -->
|
||||||
<div id="login-screen" class="screen hidden">
|
<div id="login-screen" class="screen">
|
||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<div class="logo-section">
|
<div class="login-brand">
|
||||||
<div class="logo-placeholder">
|
<div class="brand-icon">
|
||||||
<svg width="80" height="80" viewBox="0 0 80 80" fill="none">
|
<svg width="56" height="56" viewBox="0 0 56 56" fill="none">
|
||||||
<rect width="80" height="80" rx="20" fill="#e94560"/>
|
<rect width="56" height="56" rx="14" fill="url(#brandGrad)"/>
|
||||||
<path d="M25 40 L40 25 L55 40 L40 55 Z" fill="white"/>
|
<path d="M18 28 L28 18 L38 28 L28 38 Z" fill="white" opacity="0.9"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="brandGrad" x1="0" y1="0" x2="56" y2="56">
|
||||||
|
<stop offset="0%" stop-color="#e94560"/>
|
||||||
|
<stop offset="100%" stop-color="#ff6b6b"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="app-title">ZernMC Launcher</h1>
|
<h1 class="brand-title">ZernMC</h1>
|
||||||
<p class="app-version">v<span id="version">1.0.9</span></p>
|
<p class="brand-sub">Launcher <span id="version">1.0.9</span></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form id="login-form" class="login-form">
|
<form id="login-form" class="login-form">
|
||||||
<div class="input-group">
|
<div class="field">
|
||||||
<input type="text" id="username" name="username" placeholder="Имя пользователя" required autocomplete="username">
|
<input type="text" id="username" placeholder="Username" autocomplete="username" required>
|
||||||
|
<label for="username">Username</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="field">
|
||||||
<input type="password" id="password" name="password" placeholder="Пароль" required autocomplete="current-password">
|
<input type="password" id="password" placeholder="Password" autocomplete="current-password" required>
|
||||||
|
<label for="password">Password</label>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn-primary">
|
<p id="login-error" class="error-msg hidden"></p>
|
||||||
<span class="btn-text">Войти</span>
|
<button type="submit" class="btn-primary" id="login-btn">
|
||||||
<div class="btn-loader hidden"></div>
|
<span class="btn-label">Sign In</span>
|
||||||
|
<div class="spinner hidden"></div>
|
||||||
</button>
|
</button>
|
||||||
<p id="login-error" class="error-message hidden"></p>
|
<p class="login-hint">New account will be created automatically on first login</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading Overlay -->
|
||||||
|
<div id="loading-overlay" class="overlay hidden">
|
||||||
|
<div class="loader-ring"></div>
|
||||||
|
<p class="loader-text">Loading...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Main Screen -->
|
<!-- Main Screen -->
|
||||||
<div id="main-screen" class="screen hidden">
|
<div id="main-screen" class="screen hidden">
|
||||||
<div class="main-layout">
|
<div class="shell">
|
||||||
<!-- Left Sidebar -->
|
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<div class="sidebar-header">
|
<div class="sidebar-top">
|
||||||
<div class="logo-small">
|
<div class="sidebar-brand">
|
||||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none">
|
<svg width="32" height="32" viewBox="0 0 56 56" fill="none">
|
||||||
<rect width="40" height="40" rx="10" fill="#e94560"/>
|
<rect width="56" height="56" rx="14" fill="url(#brandGrad2)"/>
|
||||||
<path d="M12 20 L20 12 L28 20 L20 28 Z" fill="white"/>
|
<path d="M18 28 L28 18 L38 28 L28 38 Z" fill="white" opacity="0.9"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="brandGrad2" x1="0" y1="0" x2="56" y2="56">
|
||||||
|
<stop offset="0%" stop-color="#e94560"/>
|
||||||
|
<stop offset="100%" stop-color="#ff6b6b"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
<div class="sidebar-brand-text">
|
||||||
<div class="header-info">
|
<span class="sidebar-brand-name">ZernMC</span>
|
||||||
<h1 class="header-title">ZernMC</h1>
|
<span class="sidebar-brand-ver">v<span id="header-version">1.0.9</span></span>
|
||||||
<span class="header-version">v<span id="header-version">1.0.9</span></span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sidebar-content">
|
<nav class="sidebar-nav">
|
||||||
<div class="instances-section">
|
<button class="nav-btn active" data-view="packs" title="Packs">
|
||||||
<h3 class="section-label">ZernMC сборки</h3>
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>
|
||||||
<div id="zernmc-instances-list" class="instances-list">
|
Packs
|
||||||
<!-- ZernMC instances -->
|
</button>
|
||||||
|
<button class="nav-btn" data-view="news" title="News">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-2 2Zm0 0a2 2 0 0 1-2-2v-9h4"/><path d="M18 14h-8"/><path d="M15 18h-5"/><path d="M10 6h8v4h-8V6Z"/></svg>
|
||||||
|
News
|
||||||
|
</button>
|
||||||
|
<button class="nav-btn" data-view="settings" title="Settings">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
|
||||||
|
Settings
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="sidebar-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<span class="section-title">Server Packs</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="server-packs-list" class="pack-list"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="instances-section">
|
<div class="sidebar-section" id="local-packs-section">
|
||||||
<h3 class="section-label">Локальные сборки</h3>
|
<div class="section-header">
|
||||||
<div id="local-instances-list" class="instances-list">
|
<span class="section-title">Local Packs</span>
|
||||||
<!-- Local instances -->
|
<button class="btn-icon" id="add-pack-btn" title="Add pack">
|
||||||
</div>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="download-btn" class="btn-download">
|
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
|
||||||
<polyline points="7 10 12 15 17 10"/>
|
|
||||||
<line x1="12" y1="15" x2="12" y2="3"/>
|
|
||||||
</svg>
|
|
||||||
Скачать сборку
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="local-packs-list" class="pack-list"></div>
|
||||||
<div class="sidebar-footer">
|
|
||||||
<span class="username-display" id="username-display"></span>
|
|
||||||
<div class="account-badges">
|
|
||||||
<span id="account-status" class="badge"></span>
|
|
||||||
<span id="account-role" class="badge role-badge"></span>
|
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-logout" id="logout-btn" title="Выйти">
|
</div>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
|
<div class="sidebar-bottom">
|
||||||
<polyline points="16 17 21 12 16 7"/>
|
<div class="user-card">
|
||||||
<line x1="21" y1="12" x2="9" y2="12"/>
|
<div class="user-avatar" id="user-avatar">Z</div>
|
||||||
</svg>
|
<div class="user-info">
|
||||||
</button>
|
<span class="user-name" id="username-display">Player</span>
|
||||||
<button class="btn-logout" id="close-btn" title="Закрыть" onclick="app.shutdownLauncher()">
|
<span class="user-badges">
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<span id="account-status" class="badge badge-free">FREE</span>
|
||||||
<line x1="18" y1="6" x2="6" y2="18"/>
|
<span id="account-role" class="badge badge-role hidden"></span>
|
||||||
<line x1="6" y1="6" x2="18" y2="18"/>
|
</span>
|
||||||
</svg>
|
</div>
|
||||||
|
<button class="btn-icon" id="logout-btn" title="Log out">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<main class="content">
|
||||||
<main class="main-content">
|
<!-- Packs View -->
|
||||||
<div class="logs-section">
|
<div id="view-packs" class="view active">
|
||||||
<div class="logs-header">
|
<div class="view-header">
|
||||||
<h2>Логи</h2>
|
<div>
|
||||||
<button class="btn-clear-logs" id="clear-logs">Очистить</button>
|
<h2 class="view-title" id="selected-pack-title">Select a pack</h2>
|
||||||
|
<p class="view-subtitle" id="selected-pack-meta">Choose a pack from the sidebar to get started</p>
|
||||||
|
</div>
|
||||||
|
<div class="view-actions">
|
||||||
|
<button id="update-btn" class="btn-secondary hidden">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.65 6.35A7.958 7.958 0 0012 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0112 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
<button id="delete-pack-btn" class="btn-secondary btn-danger hidden">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pack-detail" id="pack-detail">
|
||||||
|
<div class="pack-empty" id="pack-empty-state">
|
||||||
|
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" opacity="0.2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg>
|
||||||
|
<h3>No pack selected</h3>
|
||||||
|
<p>Select a pack from the sidebar or add a new one</p>
|
||||||
|
</div>
|
||||||
|
<div id="pack-detail-content" class="pack-detail-content hidden">
|
||||||
|
<div class="pack-hero">
|
||||||
|
<div class="pack-icon">
|
||||||
|
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 id="detail-name" class="detail-name">pack</h3>
|
||||||
|
<div class="detail-tags">
|
||||||
|
<span class="tag tag-mc" id="detail-mc">1.21</span>
|
||||||
|
<span class="tag tag-loader" id="detail-loader">fabric</span>
|
||||||
|
<span class="tag tag-server hidden" id="detail-server">v1</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pack-stats">
|
||||||
|
<div class="stat"><span class="stat-value" id="detail-loader-ver">-</span><span class="stat-label">Loader Ver</span></div>
|
||||||
|
<div class="stat"><span class="stat-value" id="detail-files">0</span><span class="stat-label">Files</span></div>
|
||||||
|
<div class="stat"><span class="stat-value" id="detail-size">-</span><span class="stat-label">Size</span></div>
|
||||||
|
</div>
|
||||||
|
<div id="pack-description" class="pack-description">
|
||||||
|
<p id="pack-description-text" class="pack-description-text">Loading description...</p>
|
||||||
|
<div id="pack-gallery" class="pack-gallery">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="play-bar" id="play-bar">
|
||||||
|
<div class="play-bar-info">
|
||||||
|
<span id="play-bar-name">Select a pack</span>
|
||||||
|
</div>
|
||||||
|
<button id="play-btn" class="btn-play" disabled>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||||
|
Play
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- News View -->
|
||||||
|
<div id="view-news" class="view">
|
||||||
|
<div class="view-header">
|
||||||
|
<h2 class="view-title">News</h2>
|
||||||
|
</div>
|
||||||
|
<div class="news-grid">
|
||||||
|
<article class="news-card news-placeholder">
|
||||||
|
<div class="news-card-badge">Coming Soon</div>
|
||||||
|
<h3>ZernMC Server Updates</h3>
|
||||||
|
<p>News and announcements will appear here. Stay tuned for the latest updates about the server and launcher.</p>
|
||||||
|
<time>Soon</time>
|
||||||
|
</article>
|
||||||
|
<article class="news-card news-placeholder">
|
||||||
|
<div class="news-card-badge">Info</div>
|
||||||
|
<h3>Launcher v1.0.9</h3>
|
||||||
|
<p>English UI, JavaFX redesign, improved pack management, and more. Check the GitHub for the full changelog.</p>
|
||||||
|
<time>v1.0.9</time>
|
||||||
|
</article>
|
||||||
|
<article class="news-card news-placeholder">
|
||||||
|
<div class="news-card-badge">Guide</div>
|
||||||
|
<h3>Getting Started</h3>
|
||||||
|
<p>Install a pack, activate your pass via the website, and start playing. Need help? Contact a moderator.</p>
|
||||||
|
<time>Guide</time>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Settings View -->
|
||||||
|
<div id="view-settings" class="view">
|
||||||
|
<div class="view-header">
|
||||||
|
<h2 class="view-title">Settings</h2>
|
||||||
|
</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="setting-card">
|
||||||
|
<div class="setting-info">
|
||||||
|
<h4>Activate Pass</h4>
|
||||||
|
<p>Enter your pass code to access server packs</p>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control setting-pass">
|
||||||
|
<input type="text" id="pass-code" placeholder="Pass code" class="pass-input">
|
||||||
|
<button id="activate-pass-btn" class="btn-primary btn-sm">Activate</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-card">
|
||||||
|
<div class="setting-info">
|
||||||
|
<h4>Allocated RAM</h4>
|
||||||
|
<p id="ram-info">Loading...</p>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control">
|
||||||
|
<input type="range" id="ram-slider" min="1024" max="16384" step="512" value="4096">
|
||||||
|
<span class="setting-value" id="ram-value">4 GB</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-card">
|
||||||
|
<div class="setting-info">
|
||||||
|
<h4>Game Resolution</h4>
|
||||||
|
<p>Width x Height</p>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control" style="gap:6px">
|
||||||
|
<input type="number" id="win-width" min="640" max="7680" step="1" value="1280" class="setting-input" style="width:80px">
|
||||||
|
<span style="color:var(--text-muted)">x</span>
|
||||||
|
<input type="number" id="win-height" min="480" max="4320" step="1" value="720" class="setting-input" style="width:80px">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-card">
|
||||||
|
<div class="setting-info">
|
||||||
|
<h4>Extra JVM Arguments</h4>
|
||||||
|
<p>Additional Java VM options</p>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control">
|
||||||
|
<input type="text" id="jvm-args" placeholder="-XX:+UseZGC" class="setting-input" style="width:280px">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-card">
|
||||||
|
<div class="setting-info">
|
||||||
|
<h4>Java Path</h4>
|
||||||
|
<p id="java-path">~/.zernmc/jre/</p>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control">
|
||||||
|
<input type="text" id="java-path-input" placeholder="java" class="setting-input" style="width:280px">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-card">
|
||||||
|
<div class="setting-info">
|
||||||
|
<h4>Server</h4>
|
||||||
|
<p id="server-url">http://87.120.187.36:1582</p>
|
||||||
|
</div>
|
||||||
|
<div class="setting-control">
|
||||||
|
<span class="setting-badge" id="server-status">Checking...</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="logs-container" class="logs-container">
|
|
||||||
<div class="log-entry info">Ожидание запуска...</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Right Panel - Play Button -->
|
|
||||||
<div class="right-panel">
|
|
||||||
<button id="play-btn" class="btn-play">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
|
||||||
<polygon points="5 3 19 12 5 21 5 3"/>
|
|
||||||
</svg>
|
|
||||||
ИГРАТЬ
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Download Modal -->
|
<!-- Install Modal -->
|
||||||
<div id="download-modal" class="modal hidden">
|
<div id="install-modal" class="modal-backdrop hidden">
|
||||||
<div class="modal-content">
|
<div class="modal">
|
||||||
<div class="modal-header">
|
<div class="modal-head">
|
||||||
<h2>Скачать сборку</h2>
|
<h3>Install Pack</h3>
|
||||||
<button class="modal-close" id="close-download-modal">×</button>
|
<button class="modal-close" id="close-modal-btn">×</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
<div class="modal-tabs">
|
<div class="modal-tabs">
|
||||||
<button class="tab-btn active" data-tab="zernmc">ZernMC сборки</button>
|
<button class="modal-tab active" data-tab="zernmc">Server Pack</button>
|
||||||
<button class="tab-btn" data-tab="vanilla">Чистый Minecraft</button>
|
<button class="modal-tab" data-tab="custom">Custom</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tab-zernmc" class="tab-content active">
|
<div id="tab-zernmc" class="modal-tab-content active">
|
||||||
<div class="form-group">
|
<div class="field">
|
||||||
<label>Выберите сборку</label>
|
<label>Server Pack</label>
|
||||||
<select id="zernmc-pack-select" class="select-input">
|
<select id="zernmc-pack-select">
|
||||||
<option value="">Загрузка...</option>
|
<option value="">Loading...</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="field">
|
||||||
<label>Название сборки (системное)</label>
|
<label>Local Name</label>
|
||||||
<input type="text" id="zernmc-instance-name" class="text-input" placeholder="my-zernmc-pack">
|
<input type="text" id="zernmc-instance-name" placeholder="my-cool-pack">
|
||||||
</div>
|
</div>
|
||||||
<button id="install-zernmc-btn" class="btn-install">
|
<button id="install-zernmc-btn" class="btn-primary">Download & Install</button>
|
||||||
Скачать и установить
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tab-vanilla" class="tab-content">
|
<div id="tab-custom" class="modal-tab-content">
|
||||||
<div class="form-group">
|
<div class="field">
|
||||||
<label>Версия Minecraft</label>
|
<label>Minecraft Version</label>
|
||||||
<div class="custom-dropdown" id="mc-version-dropdown">
|
<div class="select-wrap">
|
||||||
<div class="dropdown-trigger">
|
<select id="mc-version-select"><option>Loading...</option></select>
|
||||||
<span class="dropdown-value">Выберите версию</span>
|
|
||||||
<span class="dropdown-arrow">▼</span>
|
|
||||||
</div>
|
|
||||||
<div class="dropdown-list"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="field">
|
||||||
<label>Лоадер</label>
|
<label>Mod Loader</label>
|
||||||
<div class="custom-dropdown" id="loader-dropdown">
|
<div class="select-wrap">
|
||||||
<div class="dropdown-trigger">
|
<select id="loader-select">
|
||||||
<span class="dropdown-value">Vanilla (без лоадера)</span>
|
<option value="vanilla">Vanilla (no loader)</option>
|
||||||
<span class="dropdown-arrow">▼</span>
|
<option value="fabric">Fabric</option>
|
||||||
</div>
|
<option value="forge">Forge</option>
|
||||||
<div class="dropdown-list">
|
<option value="neoforge">NeoForge</option>
|
||||||
<div class="dropdown-item selected" data-value="vanilla">Vanilla (без лоадера)</div>
|
</select>
|
||||||
<div class="dropdown-item" data-value="fabric">Fabric</div>
|
|
||||||
<div class="dropdown-item" data-value="forge">Forge</div>
|
|
||||||
<div class="dropdown-item" data-value="neoforge">NeoForge</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="field hidden" id="loader-ver-field">
|
||||||
<div id="loader-version-group" class="form-group hidden">
|
<label>Loader Version</label>
|
||||||
<label>Версия лоадера</label>
|
<div class="select-wrap">
|
||||||
<div class="custom-dropdown" id="loader-version-dropdown">
|
<select id="loader-ver-select"><option>Select loader version</option></select>
|
||||||
<div class="dropdown-trigger">
|
|
||||||
<span class="dropdown-value">Выберите версию</span>
|
|
||||||
<span class="dropdown-arrow">▼</span>
|
|
||||||
</div>
|
|
||||||
<div class="dropdown-list"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="field">
|
||||||
<label>Название сборки</label>
|
<label>Local Name</label>
|
||||||
<input type="text" id="vanilla-instance-name" class="text-input" placeholder="my-minecraft">
|
<input type="text" id="custom-instance-name" placeholder="my-minecraft">
|
||||||
</div>
|
</div>
|
||||||
<button id="install-vanilla-btn" class="btn-install">
|
<button id="install-custom-btn" class="btn-primary">Download & Install</button>
|
||||||
Скачать и установить
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="download-progress" class="download-progress hidden">
|
<div id="install-progress" class="install-progress hidden">
|
||||||
<div class="progress-bar">
|
<div class="progress-track">
|
||||||
<div class="progress-fill" id="progress-fill"></div>
|
<div class="progress-fill" id="progress-fill"></div>
|
||||||
</div>
|
</div>
|
||||||
<p class="progress-text" id="progress-text">Загрузка...</p>
|
<p class="progress-label" id="progress-label">Installing...</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Loading Overlay -->
|
<!-- Notification Toast -->
|
||||||
<div id="loading-overlay" class="loading-overlay hidden">
|
<div id="toast" class="toast hidden"></div>
|
||||||
<div class="loader"></div>
|
|
||||||
<p>Загрузка...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="launcher.js"></script>
|
<script src="launcher.js"></script>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user