diff --git a/.gitignore b/.gitignore
index f31ba5b..6fdf0b1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,8 @@ logs/
__pycache__/
./.venv/
launcher/target
+bootstrap/target
+src/target
server/builds
server/packs
server/data
@@ -9,5 +11,4 @@ jre
.vscode
dependency-reduced-pom.xml
OpenJDK21U-jre_x64_windows_hotspot_21.0.6_7.zip
-telegram-bot/
-.env
+telegram-bot/
\ No newline at end of file
diff --git a/launcher/bootstrap/Test.class b/launcher/bootstrap/Test.class
deleted file mode 100644
index 10efc1a..0000000
Binary files a/launcher/bootstrap/Test.class and /dev/null differ
diff --git a/launcher/bootstrap/Test.java b/launcher/bootstrap/Test.java
deleted file mode 100644
index cdbb0f9..0000000
--- a/launcher/bootstrap/Test.java
+++ /dev/null
@@ -1,22 +0,0 @@
-public class Test {
- public static void main(String[] args) {
- String line = "{\"version\":\"1.0.8\",\"updated_at\":\"2026-05-06T04:38:07\"}";
- System.out.println("Line: " + line);
-
- int start = line.indexOf("\"version\":\"");
- System.out.println("start (version): " + start);
-
- if (start >= 0) {
- start += 10;
- System.out.println("start + 10: " + start);
-
- int end = line.indexOf("\"", start + 1);
- System.out.println("end: " + end);
-
- if (end > start) {
- String result = line.substring(start, end);
- System.out.println("Result: [" + result + "]");
- }
- }
- }
-}
diff --git a/launcher/bootstrap/Test2.class b/launcher/bootstrap/Test2.class
deleted file mode 100644
index 67436f5..0000000
Binary files a/launcher/bootstrap/Test2.class and /dev/null differ
diff --git a/launcher/bootstrap/Test2.java b/launcher/bootstrap/Test2.java
deleted file mode 100644
index 67a8a66..0000000
--- a/launcher/bootstrap/Test2.java
+++ /dev/null
@@ -1,31 +0,0 @@
-public class Test2 {
- public static void main(String[] args) {
- String[] tests = {
- "{\"version\":\"1.0.8\",\"updated_at\":\"2026-05-06T04:38:07\"}",
- "{\"version\":\"1.0.2\"}",
- "invalid json"
- };
-
- for (String line : tests) {
- String result = getVersion(line);
- System.out.println("Input: " + line);
- System.out.println("Output: [" + result + "]");
- System.out.println("Length: " + result.length());
- System.out.println();
- }
- }
-
- static String getVersion(String line) {
- if (line != null && line.contains("version")) {
- int start = line.indexOf("\"version\":\"");
- if (start >= 0) {
- start += 10;
- int end = line.indexOf("\"", start + 1);
- if (end > start) {
- return line.substring(start, end);
- }
- }
- }
- return "unknown";
- }
-}
diff --git a/launcher/bootstrap/pom.xml b/launcher/bootstrap/pom.xml
index 71d5a92..20cc5ce 100644
--- a/launcher/bootstrap/pom.xml
+++ b/launcher/bootstrap/pom.xml
@@ -2,9 +2,8 @@
-
4.0.0
-
+
me.sashegdev
ZernMCLauncher
@@ -13,24 +12,24 @@
zernmc-bootstrap
jar
+
ZernMC Bootstrap
- ZernMC Launcher - Bootstrap (auto-updater)
+ Bootstrap module - handles updates and Java launching
+
- org.junit.jupiter
- junit-jupiter
- 5.10.0
- test
+ com.google.code.gson
+ gson
+
+
+ org.apache.httpcomponents
+ httpclient
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
org.apache.maven.plugins
maven-shade-plugin
@@ -42,56 +41,16 @@
shade
- ../../server/builds/zernmc/zernmc.jar
+ ../../server/builds/zernmc-bootstrap.jar
me.sashegdev.zernmc.launcher.Bootstrap
-
- 1.0.8
- ZernMC Bootstrap
- ZernMC
-
-
- com.akathist.maven.plugins.launch4j
- launch4j-maven-plugin
- 2.5.0
-
-
- l4j
- package
-
- launch4j
-
-
- ../../server/builds/zernmc/zernmc.exe
- ../../server/builds/zernmc/zernmc.jar
- gui
- true
-
- lib/jre21-custom
- 21
-
-
- 1.0.8.0
- 1.0.8
- ZernMC Launcher Bootstrap
- 1.0.8.0
- 1.0.8
- ZernMC Launcher
- ZernMC
- zernmc
- zernmc.exe
-
-
-
-
-
\ No newline at end of file
diff --git a/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java b/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java
index 0cf2ba6..123aabe 100644
--- a/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java
+++ b/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java
@@ -6,13 +6,14 @@ import java.net.URL;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.regex.*;
+import java.util.List;
public class Bootstrap {
private static final String VERSION_FILE = "build.version";
- private static final String JAR_NAME = "bin/ZernMCLauncher.jar";
- private static final String BASE_URL = "http://87.120.187.36:1582/launcher/download?type=jar";
+ private static final String JAR_NAME = "zernmclauncher.jar";
+ private static final String BASE_URL = "http://87.120.187.36:1582";
private static Path baseDir;
private static Path logDir;
@@ -24,25 +25,31 @@ public class Bootstrap {
log("=== ZernMC Launcher ===");
- if (args.length > 0 && args[0].equals("--launcher")) {
- launchUI();
- return;
- }
+ // Определяем режим запуска
+ List argList = Arrays.asList(args);
+ boolean cliMode = argList.contains("--cli");
+ boolean jfxMode = !cliMode; // по умолчанию JFX
+ // Проверка и обновление лаунчера
String currentVersion = readCurrentVersion();
String serverVersion = getServerVersion();
- log("Локальная: " + currentVersion);
- log("Сервер: " + serverVersion);
+ log("Локальная версия: " + currentVersion);
+ log("Версия на сервере: " + serverVersion);
if (isNewer(serverVersion, currentVersion)) {
log("Доступно обновление!");
- downloadUpdate();
+ downloadUpdate(serverVersion);
} else {
- log("Актуально");
+ log("Версия актуальна");
}
- launchGame();
+ // Запуск в выбранном режиме
+ if (jfxMode) {
+ launchJFX();
+ } else {
+ launchCLI();
+ }
}
private static void log(String msg) {
@@ -71,14 +78,7 @@ public class Bootstrap {
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
String line = br.readLine();
if (line != null && line.contains("version")) {
- int start = line.indexOf("\"version\":\"");
- if (start >= 0) {
- start += 11;
- int end = line.indexOf("\"", start);
- if (end > start) {
- return line.substring(start, end);
- }
- }
+ return line.replaceAll(".*\"version\"\\s*:\\s*\"([^\"]+)\".*", "$1");
}
}
}
@@ -101,13 +101,15 @@ public class Bootstrap {
return false;
}
- private static void downloadUpdate() throws Exception {
- URL url = new URL(BASE_URL);
+ private static void downloadUpdate(String newVersion) throws Exception {
+ URL url = new URL(BASE_URL + "/launcher/download/jar");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
if (conn.getResponseCode() == 200) {
- Path tmp = baseDir.resolve(JAR_NAME + ".new");
+ Path jarFile = baseDir.resolve(JAR_NAME);
+ Path tmp = jarFile.resolveSibling("zernmc-launcher-new.jar");
+
try (InputStream in = conn.getInputStream();
OutputStream out = new FileOutputStream(tmp.toFile())) {
byte[] buf = new byte[8192];
@@ -121,84 +123,124 @@ public class Bootstrap {
}
log("Скачано");
- Path jarFile = baseDir.resolve(JAR_NAME);
- Path backup = baseDir.resolve(JAR_NAME + ".old");
+ Path backup = jarFile.resolveSibling(JAR_NAME + ".old");
- if (Files.exists(jarFile)) Files.move(jarFile, backup);
- Files.move(tmp, jarFile);
+ if (Files.exists(jarFile)) Files.move(jarFile, backup, StandardCopyOption.REPLACE_EXISTING);
+ Files.move(tmp, jarFile, StandardCopyOption.REPLACE_EXISTING);
if (Files.exists(backup)) Files.delete(backup);
- String newVersion = getServerVersion();
Files.writeString(baseDir.resolve(VERSION_FILE), newVersion);
log("Обновлено до v" + newVersion);
+ } else {
+ throw new IOException("Сервер вернул код: " + conn.getResponseCode());
}
}
-private static void launchUI() throws Exception {
- // Запускаем JAR файл с аргументом --cli
- Path javaBin = baseDir.resolve("lib").resolve("jre21-custom").resolve("bin").resolve("java");
- if (System.getProperty("os.name").toLowerCase().contains("windows")) {
- javaBin = javaBin.resolveSibling("java.exe");
- }
-
- if (!Files.exists(javaBin)) {
- javaBin = Paths.get(System.getProperty("java.home"), "bin", "java");
- if (System.getProperty("os.name").toLowerCase().contains("windows")) {
- javaBin = javaBin.resolveSibling("java.exe");
- }
- }
-
+ private static void launchJFX() throws Exception {
+ Path javaBin = findJava();
Path jarPath = baseDir.resolve(JAR_NAME);
- ProcessBuilder pb = new ProcessBuilder(
- javaBin.toAbsolutePath().toString(),
- "-jar",
- jarPath.toAbsolutePath().toString(),
- "--cli"
- );
- pb.directory(baseDir.toFile());
- pb.inheritIO();
- Process p = pb.start();
- int code = p.waitFor();
- System.exit(code);
- }
-
- private static void launchGame() throws Exception {
- Path javaBin = baseDir.resolve("lib").resolve("jre21-custom").resolve("bin").resolve("java");
- if (System.getProperty("os.name").toLowerCase().contains("windows")) {
- javaBin = javaBin.resolveSibling("java.exe");
- }
-
- if (!Files.exists(javaBin)) {
- javaBin = baseDir.resolve("bin").resolve("java");
- if (System.getProperty("os.name").toLowerCase().contains("windows")) {
- javaBin = javaBin.resolveSibling("java.exe");
- }
- }
-
- if (!Files.exists(javaBin)) {
- javaBin = Paths.get(System.getProperty("java.home"), "bin", "java");
- if (System.getProperty("os.name").toLowerCase().contains("windows")) {
- javaBin = javaBin.resolveSibling("java.exe");
- }
- }
-
+ log("Запуск JFX режима...");
log("Java: " + javaBin);
- log("Запуск...");
+ log("JAR: " + jarPath);
- Path jarPath = baseDir.resolve(JAR_NAME);
-
- ProcessBuilder pb = new ProcessBuilder(
- javaBin.toAbsolutePath().toString(),
- "-jar",
- jarPath.toAbsolutePath().toString(),
- "--launcher"
+ // JVM аргументы для UTF-8 и JavaFX
+ List jvmArgs = List.of(
+ "-Dfile.encoding=UTF-8",
+ "-Dsun.stdout.encoding=UTF-8",
+ "-Dsun.stderr.encoding=UTF-8"
);
+
+ // Путь к JavaFX модулям
+ Path javafxPath = baseDir.resolve("lib").resolve("javafx");
+ if (Files.exists(javafxPath)) {
+ jvmArgs = List.of(
+ "-Dfile.encoding=UTF-8",
+ "-Dsun.stdout.encoding=UTF-8",
+ "-Dsun.stderr.encoding=UTF-8",
+ "--module-path", javafxPath.toAbsolutePath().toString(),
+ "--add-modules", "javafx.controls,javafx.web"
+ );
+ }
+
+ List cmd = new ArrayList<>();
+ cmd.add(javaBin.toAbsolutePath().toString());
+ cmd.addAll(jvmArgs);
+ cmd.add("-jar");
+ cmd.add(jarPath.toAbsolutePath().toString());
+ cmd.add("--jfx");
+
+ ProcessBuilder pb = new ProcessBuilder(cmd);
pb.directory(baseDir.toFile());
pb.inheritIO();
Process p = pb.start();
int code = p.waitFor();
- log("Завершено: " + code);
+ log("Завершено с кодом: " + code);
System.exit(code);
}
+
+ private static void launchCLI() throws Exception {
+ Path javaBin = findJava();
+ Path jarPath = baseDir.resolve(JAR_NAME);
+
+ log("Запуск CLI режима...");
+ log("Java: " + javaBin);
+ log("JAR: " + jarPath);
+
+ // JVM аргументы для UTF-8
+ List jvmArgs = List.of(
+ "-Dfile.encoding=UTF-8",
+ "-Dsun.stdout.encoding=UTF-8",
+ "-Dsun.stderr.encoding=UTF-8"
+ );
+
+ List cmd = new ArrayList<>();
+ cmd.add(javaBin.toAbsolutePath().toString());
+ cmd.addAll(jvmArgs);
+ cmd.add("-jar");
+ cmd.add(jarPath.toAbsolutePath().toString());
+ cmd.add("--cli");
+
+ ProcessBuilder pb = new ProcessBuilder(cmd);
+ pb.directory(baseDir.toFile());
+ pb.inheritIO();
+ Process p = pb.start();
+ int code = p.waitFor();
+ log("Завершено с кодом: " + code);
+ System.exit(code);
+ }
+
+ private static Path findJava() {
+ String os = System.getProperty("os.name").toLowerCase();
+ String javaExe = os.contains("windows") ? "java.exe" : "java";
+
+ // Сначала ищем jre21/bin/java рядом с лаунчером
+ Path javaBin = baseDir.resolve("jre21").resolve("bin").resolve(javaExe);
+
+ // Если нет, пробуем системную Java
+ if (!Files.exists(javaBin)) {
+ javaBin = Paths.get(System.getProperty("java.home"), "bin", javaExe);
+ }
+
+ // Если и это не найдено - ищем java в PATH
+ if (!Files.exists(javaBin)) {
+ try {
+ Process p = new ProcessBuilder("which", javaExe).start();
+ if (p.waitFor() == 0) {
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
+ String path = br.readLine();
+ if (path != null) {
+ javaBin = Paths.get(path.trim());
+ }
+ }
+ }
+ } catch (Exception ignored) {}
+ }
+
+ if (!Files.exists(javaBin)) {
+ throw new RuntimeException("Java не найдена. Убедитесь, что jre21 присутствует в папке с лаунчером или Java установлена в системе");
+ }
+
+ return javaBin;
+ }
}
\ No newline at end of file
diff --git a/launcher/bootstrap/src/test/java/me/sashegdev/zernmc/launcher/BootstrapVersionTest.java b/launcher/bootstrap/src/test/java/me/sashegdev/zernmc/launcher/BootstrapVersionTest.java
deleted file mode 100644
index 8493f0b..0000000
--- a/launcher/bootstrap/src/test/java/me/sashegdev/zernmc/launcher/BootstrapVersionTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package me.sashegdev.zernmc.launcher;
-
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-
-public class BootstrapVersionTest {
-
- @Test
- public void testVersionParsing() {
- assertEquals("1.0.8", getVersion("{\"version\":\"1.0.8\",\"updated_at\":\"2026-05-06T04:38:07\"}"));
- assertEquals("1.0.2", getVersion("{\"version\":\"1.0.2\"}"));
- assertEquals("2.0.0", getVersion("{\"version\":\"2.0.0\",\"download_jar\":\"/launcher/download/jar\"}"));
- assertEquals("unknown", getVersion("invalid json"));
- assertEquals("unknown", getVersion("{\"ver\":\"1.0.8\"}"));
- assertEquals("unknown", getVersion(""));
- assertEquals("unknown", getVersion(null));
- }
-
- @Test
- public void testVersionComparison() {
- assertTrue(isNewer("1.0.8", "1.0.7"));
- assertTrue(isNewer("1.0.8", "1.0.2"));
- assertTrue(isNewer("2.0.0", "1.0.8"));
- assertFalse(isNewer("1.0.8", "1.0.8"));
- assertFalse(isNewer("1.0.7", "1.0.8"));
- assertTrue(isNewer("1.0.10", "1.0.9"));
- assertTrue(isNewer("1.0.9", "1.0.8"));
- assertFalse(isNewer("unknown", "1.0.8"));
- assertFalse(isNewer("1.0.8", "unknown"));
- }
-
- @Test
- public void testEdgeCases() {
- assertEquals("unknown", getVersion(null));
- assertEquals("unknown", getVersion(""));
- assertFalse(isNewer("unknown", "1.0.8"));
- assertFalse(isNewer("1.0.8", "unknown"));
- }
-
- private String getVersion(String line) {
- if (line != null && line.contains("version")) {
- int start = line.indexOf("\"version\":\"");
- if (start >= 0) {
- start += 11;
- int end = line.indexOf("\"", start);
- if (end > start) {
- return line.substring(start, end);
- }
- }
- }
- return "unknown";
- }
-
- private boolean isNewer(String server, String current) {
- try {
- String[] sa = server.split("\\.");
- String[] ca = current.split("\\.");
- for (int i = 0; i < Math.min(sa.length, ca.length); i++) {
- int sv = Integer.parseInt(sa[i]);
- int cv = Integer.parseInt(ca[i]);
- if (sv > cv) return true;
- if (sv < cv) return false;
- }
- return sa.length > ca.length;
- } catch (Exception ignored) {}
- return false;
- }
-}
\ No newline at end of file
diff --git a/launcher/dependency-reduced-pom.xml b/launcher/dependency-reduced-pom.xml
index 2c4af15..e79f340 100644
--- a/launcher/dependency-reduced-pom.xml
+++ b/launcher/dependency-reduced-pom.xml
@@ -3,9 +3,13 @@
4.0.0
me.sashegdev
ZernMCLauncher
- 1.0.7
+ 1.0.8
+
+ maven-surefire-plugin
+ 3.2.3
+
maven-shade-plugin
3.5.0
@@ -24,7 +28,7 @@
${project.version}
ZernMC Launcher
SashegDev
- Полностью самописный Minecraft-лаунчер. Написанный SashegDev(в основном)
+ Samopisnui Minecraft-launcher. by SashegDev
https://github.com/SashegDev/launcher
@@ -45,10 +49,11 @@
launch4j
- ../server/builds/ZernMCLauncher.exe
+ ../server/builds/ZernMCLauncher-${project.version}.exe
../server/builds/ZernMCLauncher.jar
console
false
+ me.sashegdev.zernmc.launcher.Bootstrap
jre21
21
@@ -56,13 +61,13 @@
${project.version}.0
${project.version}
- ZernMC Launcher — A Little Minecraft Launcher
+ ZernMC Launcher — just a Minecraft launcher
${project.version}.0
${project.version}
ZernMC Launcher
ZernMC(SashegDev)
ZernMCLauncher
- ZernMCLauncher.exe
+ ZernMCLauncher-${project.version}.exe
@@ -80,9 +85,15 @@
${project.version}
+
+
-
+
+
+
+
+
@@ -109,10 +120,35 @@
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.10.1
+ test
+
+
+ junit-jupiter-api
+ org.junit.jupiter
+
+
+ junit-jupiter-params
+ org.junit.jupiter
+
+
+ junit-jupiter-engine
+ org.junit.jupiter
+
+
+
+
- 21
+ ZernMC Launcher - just a minimalistic launcher by SashegDev
me.sashegdev.zernmc.launcher.Main
21
+ ZernMC
+ 21
UTF-8
+ 2026
diff --git a/launcher/launcher/.gitignore b/launcher/launcher/.gitignore
deleted file mode 100644
index 47b546a..0000000
--- a/launcher/launcher/.gitignore
+++ /dev/null
@@ -1,16 +0,0 @@
-# Maven
-*/target/
-target/
-
-# Build outputs
-server/builds/
-server/logs/
-
-# IDE
-.idea/
-*.iml
-.vscode/
-
-# OS
-.DS_Store
-Thumbs.db
\ No newline at end of file
diff --git a/launcher/launcher/pom.xml b/launcher/launcher/pom.xml
index 1985917..d5ae1ab 100644
--- a/launcher/launcher/pom.xml
+++ b/launcher/launcher/pom.xml
@@ -2,99 +2,104 @@
-
4.0.0
-
+
me.sashegdev
ZernMCLauncher
1.0.8
- zernmc-launcher
+ zernmclauncher
jar
+
ZernMC Launcher
- ZernMC Launcher - UI
+ Main launcher module with JFX UI
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.google.code.gson
+ gson
+
+
+ org.json
+ json
+
+
+
+
+ org.fusesource.jansi
+ jansi
+
+
+ org.jline
+ jline
+
+
+ me.tongfei
+ progressbar
+
+
+
+
+ commons-io
+ commons-io
+
+
+
org.openjfx
javafx-controls
21
- linux
+ win
org.openjfx
javafx-web
21
- linux
+ win
+
+
+ org.openjfx
+ javafx-graphics
+ 21
+ win
+
+
+ org.openjfx
+ javafx-base
+ 21
+ win
org.openjfx
javafx-media
21
- linux
-
-
- org.apache.httpcomponents
- httpclient
- 4.5.14
-
-
- com.fasterxml.jackson.core
- jackson-databind
- 2.15.2
-
-
- com.google.code.gson
- gson
- 2.10.1
-
-
- org.json
- json
- 20230227
-
-
- net.java.dev.jna
- jna
- 5.13.0
-
-
- org.jline
- jline
- 3.21.0
-
-
- org.fusesource.jansi
- jansi
- 2.4.0
+ win
+
+
org.junit.jupiter
junit-jupiter
- 5.10.0
- test
-
-
- org.mockito
- mockito-core
- 5.7.0
+ 5.10.1
test
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 3.1.2
-
org.apache.maven.plugins
maven-shade-plugin
@@ -106,14 +111,13 @@
shade
- ../../server/builds/zernmc/bin/ZernMCLauncher.jar
+ ../../server/builds/zernmclauncher.jar
me.sashegdev.zernmc.launcher.Main
- 1.0.8
+ ${project.version}
ZernMC Launcher
- ZernMC
@@ -121,6 +125,46 @@
+
+
+
+ com.akathist.maven.plugins.launch4j
+ launch4j-maven-plugin
+ 2.5.0
+
+
+ l4j
+ package
+
+ launch4j
+
+
+ ../../server/builds/zernmc-${project.version}.exe
+ ../../server/builds/zernmc-bootstrap.jar
+ console
+ false
+ me.sashegdev.zernmc.launcher.Bootstrap
+
+ lib/jre21
+ 21
+
+
+ ${project.version}.0
+ ${project.version}
+ ZernMC Launcher
+ ${project.version}.0
+ ${project.version}
+ ZernMC
+ ZernMC
+ zernmc
+ zernmc-${project.version}.exe
+
+
+
+
+
+
+
org.apache.maven.plugins
maven-antrun-plugin
@@ -131,17 +175,59 @@
run
-
-
+ ${project.version}
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/Main.java b/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/Main.java
deleted file mode 100644
index cbabe25..0000000
--- a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/Main.java
+++ /dev/null
@@ -1,241 +0,0 @@
-package me.sashegdev.zernmc.launcher;
-
-import me.sashegdev.zernmc.launcher.api.LauncherAPI;
-import me.sashegdev.zernmc.launcher.auth.AuthManager;
-import me.sashegdev.zernmc.launcher.menu.*;
-import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
-import me.sashegdev.zernmc.launcher.ui.jcef.UILauncher;
-import me.sashegdev.zernmc.launcher.ui.jfx.JFXLauncher;
-import me.sashegdev.zernmc.launcher.utils.*;
-import java.io.IOException;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.util.List;
-
-public class Main {
-
- private static final String CURRENT_VERSION = Version.getCurrentVersion();
- private static final LauncherAPI api = new LauncherAPI();
-
- public static void main(String[] args) throws IOException {
- System.setProperty("org.jline.terminal.disableDeprecatedProviderWarning", "true");
- System.setProperty("file.encoding", "UTF-8");
- System.setProperty("sun.err.encoding", "UTF-8");
- System.setProperty("sun.stdout.encoding", "UTF-8");
-
- if (args.length > 0 && args[0].equals("--cli")) {
- launchCLI(args);
- return;
- }
-
- try {
- launchUI(args);
- } catch (Exception e) {
- System.out.println("UI недоступен, переход в CLI режим: " + e.getMessage());
- launchCLI(args);
- }
- }
-
- private static void launchUI(String[] args) throws Exception {
- System.out.println("Запуск JFX UI...");
- JFXLauncher.main(args);
- }
-
- private static void launchCLI(String[] args) throws IOException {
- ZAnsi.install();
- System.out.print("\033[H\033[2J");
- System.out.println(ZAnsi.brightGreen("Добро пожаловать в ZernMC Launcher " + CURRENT_VERSION));
-
- ZHttpClient.checkAllServicesOnStartup();
- checkAndAutoUpdateLauncher();
-
- System.out.println(ZAnsi.cyan("Проверка авторизации..."));
- var sessionResponse = api.checkSession();
-
- if (!sessionResponse.isSuccess()) {
- LoginMenu loginMenu = new LoginMenu();
- boolean loggedIn = loginMenu.show();
- if (!loggedIn) {
- System.out.println(ZAnsi.yellow("До свидания!"));
- ZAnsi.uninstall();
- System.exit(0);
- }
- } else {
- var sessionInfo = sessionResponse.getData();
- System.out.println(ZAnsi.brightGreen("Добро пожаловать обратно, " + sessionInfo.getUsername() + "!"));
- }
-
- try {
- mainLoop();
- } catch (Exception e) {
- System.err.println(ZAnsi.brightRed("Критическая ошибка: " + e.getMessage()));
- e.printStackTrace();
- } finally {
- ZAnsi.uninstall();
- }
- }
-
- private static void checkAndAutoUpdateLauncher() {
- System.out.println(ZAnsi.cyan("Проверка обновлений лаунчера..."));
- try {
- String json = ZHttpClient.getLauncherVersionInfo();
- String serverVersion = extractVersion(json);
-
- System.out.println(ZAnsi.white("Текущая версия: ") + CURRENT_VERSION);
- System.out.println(ZAnsi.white("Версия на сервере: ") + serverVersion);
-
- if (Version.isNewer(CURRENT_VERSION, serverVersion)) {
- System.out.println(ZAnsi.brightYellow("\nДоступна новая версия лаунчера! (" + serverVersion + ")"));
- System.out.println(ZAnsi.cyan("Начинается автоматическое обновление...\n"));
- performAutoUpdate(serverVersion);
- restartLauncher();
- } else {
- System.out.println(ZAnsi.brightGreen("Лаунчер актуален."));
- }
- } catch (Exception e) {
- System.out.println(ZAnsi.yellow("Не удалось проверить обновления лаунчера."));
- System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
- }
- }
-
- private static void performAutoUpdate(String newVersion) throws Exception {
- String downloadUrl = ZHttpClient.getBaseUrl() + "/launcher/download?type=jar";
- Path currentJar = getCurrentJarPath();
- Path tempJar = currentJar.getParent().resolve("zernmc-launcher-new.jar");
-
- System.out.println(ZAnsi.cyan("Скачивание версии " + newVersion + "..."));
-
- HttpClient client = HttpClient.newBuilder().build();
- HttpRequest request = HttpRequest.newBuilder()
- .uri(java.net.URI.create(downloadUrl))
- .GET()
- .build();
-
- HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofFile(tempJar));
-
- if (response.statusCode() != 200) {
- throw new IOException("Сервер вернул код: " + response.statusCode());
- }
-
- long size = Files.size(tempJar);
- System.out.println(ZAnsi.brightGreen("Скачано успешно (" + (size / 1024) + " KB)"));
-
- Files.move(tempJar, currentJar, StandardCopyOption.REPLACE_EXISTING);
- System.out.println(ZAnsi.brightGreen("Обновление успешно установлено!"));
- }
-
- private static void restartLauncher() {
- try {
- String javaPath = System.getProperty("java.home") + "/bin/java";
- String jarPath = getCurrentJarPath().toAbsolutePath().toString();
-
- System.out.println(ZAnsi.brightGreen("Перезапуск лаунчера с новой версией..."));
-
- new ProcessBuilder(javaPath, "-jar", jarPath)
- .inheritIO()
- .start();
-
- System.exit(0);
- } catch (Exception e) {
- System.err.println(ZAnsi.brightRed("Не удалось перезапустить лаунчер."));
- System.exit(1);
- }
- }
-
- private static String extractVersion(String json) {
- try {
- return json.replaceAll(".*\"version\"\\s*:\\s*\"([^\"]+)\".*", "$1");
- } catch (Exception e) {
- return "unknown";
- }
- }
-
- private static Path getCurrentJarPath() {
- try {
- return Path.of(Main.class.getProtectionDomain()
- .getCodeSource()
- .getLocation()
- .toURI());
- } catch (Exception e) {
- return Path.of("zernmc-launcher-1.0-jar-with-dependencies.jar");
- }
- }
-
- // ====================== ГЛАВНЫЙ ЦИКЛ ======================
- private static void mainLoop() throws Exception {
- if (Config.isZernMCBuild()) {
- zernMCFlow();
- } else {
- globalFlow();
- }
- }
-
- // ====================== ZERNMC FLOW ======================
- private static void zernMCFlow() throws Exception {
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.header("=== ZernMC Private Launcher ==="));
-
- // 1. Проверка подключения к серверу
- System.out.println(ZAnsi.cyan("Проверка подключения к ZernMC серверу..."));
- try {
- String response = ZHttpClient.get("/health");
- System.out.println(ZAnsi.brightGreen("✓ Сервер доступен"));
- } catch (Exception e) {
- System.out.println(ZAnsi.brightRed("✗ Не удалось подключиться к ZernMC серверу"));
- System.out.println(ZAnsi.white("Ошибка: " + e.getMessage()));
- ConsoleUtils.pause();
- System.exit(1);
- }
-
- // 2. Авторизация
- boolean sessionRestored = AuthManager.loadSavedSession();
- if (!sessionRestored) {
- LoginMenu loginMenu = new LoginMenu();
- boolean loggedIn = loginMenu.show();
- if (!loggedIn) {
- System.exit(0);
- }
- } else {
- System.out.println(ZAnsi.brightGreen("Добро пожаловать обратно, " + AuthManager.getUsername() + "!"));
- }
-
- // 3. Запуск меню (LaunchMenu сам определит режим и вызовет нужный flow)
- LaunchMenu launchMenu = new LaunchMenu();
- launchMenu.show(); // ← Здесь будет вызван showZernMCOnly() внутри
- }
-
- // ====================== GLOBAL FLOW ======================
- private static void globalFlow() throws Exception {
- while (true) {
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.header("=== ZernMC Launcher ==="));
-
- List options = List.of(
- "Запустить игру",
- "Проверка обновлений",
- "Настройки",
- "Проверка подключения к серверам",
- "Выход"
- );
-
- ArrowMenu menu = new ArrowMenu("Главное меню", options);
- int choice = menu.show();
-
- if (choice == -1 || choice == 4) {
- System.out.println(ZAnsi.yellow("До свидания!"));
- break;
- }
-
- switch (choice) {
- case 0 -> new LaunchMenu().show(); // обычный LaunchMenu
- case 1 -> new UpdateMenu().show();
- case 2 -> new SettingsMenu().show();
- case 3 -> new ServerCheckMenu().show();
- }
- }
- }
-}
\ No newline at end of file
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/LaunchServer.java b/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/LaunchServer.java
deleted file mode 100644
index 2bb939f..0000000
--- a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/LaunchServer.java
+++ /dev/null
@@ -1,254 +0,0 @@
-package me.sashegdev.zernmc.launcher.ui.jcef;
-
-import com.google.gson.Gson;
-import com.sun.net.httpserver.HttpServer;
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.Headers;
-import me.sashegdev.zernmc.launcher.api.ApiResponse;
-import me.sashegdev.zernmc.launcher.api.LauncherAPI;
-import me.sashegdev.zernmc.launcher.auth.AuthManager;
-import me.sashegdev.zernmc.launcher.minecraft.Instance;
-import me.sashegdev.zernmc.launcher.minecraft.InstanceManager;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executors;
-
-public class LaunchServer {
- private static final int PORT = 8080;
- private final LauncherAPI api;
- private final UIBridge bridge;
- private HttpServer server;
- private final Gson gson = new Gson();
-
- public LaunchServer(UIBridge bridge) {
- this.api = new LauncherAPI();
- this.bridge = bridge;
- }
-
- public void start() throws IOException {
- server = HttpServer.create(new InetSocketAddress("localhost", PORT), 0);
-
- server.createContext("/api/login", this::handleLogin);
- server.createContext("/api/account", this::handleAccount);
- server.createContext("/api/instances", this::handleInstances);
- server.createContext("/api/launch", this::handleLaunch);
- server.createContext("/api/install", this::handleInstall);
- server.createContext("/api/logs", this::handleLogs);
- server.createContext("/api/exit", this::handleExit);
- server.createContext("/ui/", this::handleStatic);
-
- server.setExecutor(Executors.newCachedThreadPool());
- server.start();
-
- bridge.log("HTTP сервер запущен на порту " + PORT);
- }
-
- public void stop() {
- if (server != null) {
- server.stop(0);
- }
- }
-
- private void handleLogin(HttpExchange exchange) throws IOException {
- if (!"POST".equals(exchange.getRequestMethod())) {
- sendJson(exchange, ApiResponse.error("Метод не поддерживается"));
- return;
- }
-
- try {
- Map body = parseJson(exchange.getRequestBody());
- String username = body.get("username");
- String password = body.get("password");
-
- var result = api.login(username, password);
- if (result.isSuccess()) {
- Map data = new HashMap<>();
- data.put("username", result.getData().getUsername());
- data.put("token", result.getData().getToken());
- sendJson(exchange, ApiResponse.success(data));
- bridge.log("Пользователь вошел: " + username);
- } else {
- sendJson(exchange, ApiResponse.error(result.getError()));
- }
- } catch (Exception e) {
- sendJson(exchange, ApiResponse.error("Ошибка: " + e.getMessage()));
- }
- }
-
- private void handleAccount(HttpExchange exchange) throws IOException {
- if (!"GET".equals(exchange.getRequestMethod())) {
- sendJson(exchange, ApiResponse.error("Метод не поддерживается"));
- return;
- }
-
- if (!api.isLoggedIn()) {
- sendJson(exchange, ApiResponse.error("Не авторизован"));
- return;
- }
-
- try {
- Map data = new HashMap<>();
- data.put("username", api.getCurrentUsername());
- data.put("passActive", AuthManager.hasActivePass());
- sendJson(exchange, ApiResponse.success(data));
- } catch (Exception e) {
- sendJson(exchange, ApiResponse.error(e.getMessage()));
- }
- }
-
- private void handleInstances(HttpExchange exchange) throws IOException {
- if (!"GET".equals(exchange.getRequestMethod())) {
- sendJson(exchange, ApiResponse.error("Метод не поддерживается"));
- return;
- }
-
- try {
- var result = api.getAllInstances();
- sendJson(exchange, result);
- } catch (Exception e) {
- sendJson(exchange, ApiResponse.error(e.getMessage()));
- }
- }
-
- private void handleLaunch(HttpExchange exchange) throws IOException {
- if (!"POST".equals(exchange.getRequestMethod())) {
- sendJson(exchange, ApiResponse.error("Метод не поддерживается"));
- return;
- }
-
- if (!api.isLoggedIn()) {
- sendJson(exchange, ApiResponse.error("Не авторизован"));
- return;
- }
-
- try {
- Map body = parseJson(exchange.getRequestBody());
- String name = body.get("name");
-
- var result = api.launch(name);
- if (result.isSuccess()) {
- Map data = new HashMap<>();
- data.put("pid", result.getData().getPid());
- data.put("status", result.getData().getStatus());
- sendJson(exchange, ApiResponse.success(data));
- bridge.log("Запущена сборка: " + name);
- } else {
- sendJson(exchange, ApiResponse.error(result.getError()));
- }
- } catch (Exception e) {
- sendJson(exchange, ApiResponse.error("Ошибка запуска: " + e.getMessage()));
- }
- }
-
- private void handleInstall(HttpExchange exchange) throws IOException {
- if (!"POST".equals(exchange.getRequestMethod())) {
- sendJson(exchange, ApiResponse.error("Метод не поддерживается"));
- return;
- }
-
- if (!api.isLoggedIn()) {
- sendJson(exchange, ApiResponse.error("Не авторизован"));
- return;
- }
-
- try {
- Map body = parseJson(exchange.getRequestBody());
- String name = body.get("name");
- String version = body.get("version");
- String loader = body.get("loader");
-
- bridge.log("Установка сборки: " + name + " " + version + " " + loader);
-
- var createResult = api.instances().createInstance(name);
- if (!createResult.isSuccess()) {
- sendJson(exchange, ApiResponse.error(createResult.getError()));
- return;
- }
-
- Instance instance = InstanceManager.getInstance(name);
- if (instance != null) {
- instance.setMinecraftVersion(version);
- instance.setLoaderType(loader);
- }
-
- sendJson(exchange, ApiResponse.success(true));
- bridge.log("Сборка установлена: " + name);
- } catch (Exception e) {
- sendJson(exchange, ApiResponse.error("Ошибка установки: " + e.getMessage()));
- }
- }
-
- private void handleLogs(HttpExchange exchange) throws IOException {
- String logs = bridge.getLogs();
- sendJson(exchange, ApiResponse.success(logs));
- }
-
- private void handleExit(HttpExchange exchange) throws IOException {
- bridge.log("Завершение работы...");
- System.exit(0);
- }
-
- private void handleStatic(HttpExchange exchange) throws IOException {
- String path = exchange.getRequestURI().getPath();
- if (path.equals("/ui/") || path.equals("/ui")) {
- path = "/ui/index.html";
- }
-
- var resource = getClass().getResource(path);
-
- if (resource == null) {
- exchange.sendResponseHeaders(404, 0);
- exchange.close();
- return;
- }
-
- try {
- byte[] content = resource.openStream().readAllBytes();
- String contentType = getContentType(path);
-
- exchange.getResponseHeaders().set("Content-Type", contentType);
- exchange.sendResponseHeaders(200, content.length);
-
- OutputStream os = exchange.getResponseBody();
- os.write(content);
- os.close();
- } catch (IOException ignored) {}
- }
-
- private String getContentType(String path) {
- if (path.endsWith(".html")) return "text/html; charset=utf-8";
- if (path.endsWith(".css")) return "text/css; charset=utf-8";
- if (path.endsWith(".js")) return "application/javascript; charset=utf-8";
- return "text/plain";
- }
-
- @SuppressWarnings("unchecked")
- private Map parseJson(InputStream body) {
- try {
- String json = new String(body.readAllBytes(), StandardCharsets.UTF_8);
- return gson.fromJson(json, Map.class);
- } catch (Exception e) {
- return new HashMap<>();
- }
- }
-
- private void sendJson(HttpExchange exchange, ApiResponse response) {
- try {
- String json = gson.toJson(response);
- byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
-
- exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
- exchange.sendResponseHeaders(200, bytes.length);
-
- OutputStream os = exchange.getResponseBody();
- os.write(bytes);
- os.close();
- } catch (IOException ignored) {}
- }
-}
\ No newline at end of file
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/UIBridge.java b/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/UIBridge.java
deleted file mode 100644
index 9768e80..0000000
--- a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/UIBridge.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package me.sashegdev.zernmc.launcher.ui.jcef;
-
-public class UIBridge {
- public void log(String message) {
- System.out.println("[UI] " + message);
- }
-
- public String getLogs() {
- return "";
- }
-}
\ No newline at end of file
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/UILauncher.java b/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/UILauncher.java
deleted file mode 100644
index 5c99b4f..0000000
--- a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/UILauncher.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package me.sashegdev.zernmc.launcher.ui.jcef;
-
-import me.sashegdev.zernmc.launcher.api.LauncherAPI;
-
-import java.awt.*;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-import java.net.URI;
-
-public class UILauncher {
- private static final String APP_TITLE = "ZernMC Launcher";
- private final LauncherAPI api;
- private final UIBridge bridge;
- private LaunchServer server;
-
- public UILauncher() {
- this.api = new LauncherAPI();
- this.bridge = new UIBridge();
- }
-
- public void launch() throws Exception {
- redirectSystemLogs();
- bridge.log("Запуск UI...");
-
- server = new LaunchServer(bridge);
- server.start();
-
- openBrowser();
-
- Runtime.getRuntime().addShutdownHook(new Thread(() -> {
- bridge.log("Выключение...");
- if (server != null) server.stop();
- }));
-
- Thread.currentThread().join();
- }
-
- private void openBrowser() {
- String url = "http://localhost:8080/ui/";
- bridge.log("Открытие браузера: " + url);
-
- try {
- if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
- Desktop.getDesktop().browse(URI.create(url));
- bridge.log("Браузер открыт");
- } else {
- bridge.log("Desktop browsing not supported");
- }
- } catch (Exception e) {
- bridge.log("Ошибка открытия браузера: " + e.getMessage());
- }
- }
-
- private void redirectSystemLogs() {
- PrintStream originalOut = System.out;
- PrintStream originalErr = System.err;
-
- System.setOut(new PrintStream(new ByteArrayOutputStream() {
- @Override
- public void write(byte[] b, int off, int len) {
- String line = new String(b, off, len).trim();
- if (!line.isEmpty()) {
- bridge.log(line);
- }
- try {
- originalOut.write(b, off, len);
- } catch (Exception ignored) {}
- }
-
- @Override
- public void write(int b) {
- try {
- originalOut.write(b);
- } catch (Exception ignored) {}
- }
- }));
-
- System.setErr(new PrintStream(new ByteArrayOutputStream() {
- @Override
- public void write(byte[] b, int off, int len) {
- String line = new String(b, off, len).trim();
- if (!line.isEmpty()) {
- bridge.log("[ERROR] " + line);
- }
- try {
- originalErr.write(b, off, len);
- } catch (Exception ignored) {}
- }
- }));
- }
-
- public static void main(String[] args) {
- try {
- UILauncher launcher = new UILauncher();
- launcher.launch();
- } catch (Exception e) {
- System.err.println("UI launch failed: " + e.getMessage());
- e.printStackTrace();
- System.exit(1);
- }
- }
-}
\ No newline at end of file
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java b/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java
deleted file mode 100644
index 77dd181..0000000
--- a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java
+++ /dev/null
@@ -1,326 +0,0 @@
-package me.sashegdev.zernmc.launcher.ui.jfx;
-
-import javafx.application.Application;
-import javafx.scene.Scene;
-import javafx.scene.web.WebView;
-import javafx.scene.web.WebEngine;
-import javafx.stage.Stage;
-import javafx.concurrent.Worker;
-import com.google.gson.Gson;
-import me.sashegdev.zernmc.launcher.api.LauncherAPI;
-import me.sashegdev.zernmc.launcher.auth.AuthManager;
-import me.sashegdev.zernmc.launcher.minecraft.Instance;
-import me.sashegdev.zernmc.launcher.minecraft.InstanceManager;
-import me.sashegdev.zernmc.launcher.utils.Config;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetSocketAddress;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executors;
-import com.sun.net.httpserver.HttpServer;
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.Headers;
-
-public class JFXLauncher extends Application {
- private static final int PORT = 8080;
- private static final String APP_TITLE = "ZernMC Launcher";
- private final LauncherAPI api = new LauncherAPI();
- private final Gson gson = new Gson();
- private HttpServer server;
- private StringBuilder logBuffer = new StringBuilder();
- private Stage mainStage;
-
- public static void main(String[] args) {
- launch(args);
- }
-
- @Override
- public void start(Stage stage) {
- this.mainStage = stage;
-
- try {
- log("Запуск JFX UI...");
- startServer();
-
- WebView webView = new WebView();
- WebEngine engine = webView.getEngine();
- engine.setJavaScriptEnabled(true);
-
- engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
- if (newState == Worker.State.SUCCEEDED) {
- log("Страница загружена");
- }
- });
-
- String url = "http://localhost:" + PORT + "/ui/";
- engine.load(url);
-
- stage.setTitle(APP_TITLE);
- stage.setWidth(1200);
- stage.setHeight(800);
- stage.setScene(new Scene(webView));
- stage.show();
-
- log("Окно отображено");
-
- stage.setOnCloseRequest(e -> {
- log("Закрытие...");
- stopServer();
- });
-
- } catch (Exception e) {
- log("Ошибка: " + e.getMessage());
- e.printStackTrace();
- throw new RuntimeException(e);
- }
- }
-
- private void startServer() throws Exception {
- server = HttpServer.create(new InetSocketAddress("localhost", PORT), 0);
-
- server.createContext("/api/login", this::handleLogin);
- server.createContext("/api/account", this::handleAccount);
- server.createContext("/api/instances", this::handleInstances);
- server.createContext("/api/launch", this::handleLaunch);
- server.createContext("/api/install", this::handleInstall);
- server.createContext("/api/logs", this::handleLogs);
- server.createContext("/api/logs/instance", this::handleInstanceLogs);
- server.createContext("/api/exit", this::handleExit);
- server.createContext("/ui/", this::handleStatic);
-
- server.setExecutor(Executors.newCachedThreadPool());
- server.start();
-
- log("HTTP сервер на порту " + PORT);
- }
-
- private void stopServer() {
- if (server != null) server.stop(0);
- }
-
- private void handleLogin(HttpExchange exchange) {
- try {
- if (!"POST".equals(exchange.getRequestMethod())) {
- sendJson(exchange, Map.of("success", false, "error", "Метод не поддерживается"));
- return;
- }
-
- Map body = parseJson(exchange.getRequestBody());
- String username = body.get("username");
- String password = body.get("password");
-
- var result = api.login(username, password);
- if (result.isSuccess()) {
- Map data = new HashMap<>();
- data.put("username", result.getData().getUsername());
- data.put("token", result.getData().getToken());
- sendJson(exchange, Map.of("success", true, "data", data));
- log("Вход: " + 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 handleAccount(HttpExchange exchange) {
- try {
- if (!api.isLoggedIn()) {
- sendJson(exchange, Map.of("success", false, "error", "Не авторизован"));
- return;
- }
- Map data = new HashMap<>();
- data.put("username", api.getCurrentUsername());
- data.put("passActive", AuthManager.hasActivePass());
- sendJson(exchange, Map.of("success", true, "data", data));
- } catch (Exception e) {
- sendJson(exchange, Map.of("success", false, "error", e.getMessage()));
- }
- }
-
- private void handleInstances(HttpExchange exchange) {
- try {
- var result = api.getAllInstances();
- Map response = new HashMap<>();
- response.put("success", result.isSuccess());
- if (result.isSuccess()) {
- response.put("data", result.getData());
- } else {
- response.put("error", result.getError());
- }
- sendJson(exchange, response);
- } catch (Exception e) {
- sendJson(exchange, Map.of("success", false, "error", e.getMessage()));
- }
- }
-
- private void handleLaunch(HttpExchange exchange) {
- try {
- if (!api.isLoggedIn()) {
- sendJson(exchange, Map.of("success", false, "error", "Не авторизован"));
- return;
- }
-
- Map body = parseJson(exchange.getRequestBody());
- String name = body.get("name");
-
- var result = api.launch(name);
- if (result.isSuccess()) {
- Map data = new HashMap<>();
- data.put("pid", result.getData().getPid());
- data.put("status", result.getData().getStatus());
- sendJson(exchange, Map.of("success", true, "data", data));
- log("Запущено: " + name);
- } else {
- sendJson(exchange, Map.of("success", false, "error", result.getError()));
- }
- } catch (Exception e) {
- sendJson(exchange, Map.of("success", false, "error", e.getMessage()));
- }
- }
-
- private void handleInstall(HttpExchange exchange) {
- try {
- if (!api.isLoggedIn()) {
- sendJson(exchange, Map.of("success", false, "error", "Не авторизован"));
- return;
- }
-
- Map body = parseJson(exchange.getRequestBody());
- String name = body.get("name");
- String version = body.get("version");
- String loader = body.get("loader");
-
- log("Установка: " + name + " " + version + " " + loader);
-
- var createResult = api.instances().createInstance(name);
- if (!createResult.isSuccess()) {
- sendJson(exchange, Map.of("success", false, "error", createResult.getError()));
- return;
- }
-
- Instance instance = InstanceManager.getInstance(name);
- if (instance != null) {
- instance.setMinecraftVersion(version);
- instance.setLoaderType(loader);
- }
-
- sendJson(exchange, Map.of("success", true, "data", true));
- log("Установлено: " + name);
- } catch (Exception e) {
- sendJson(exchange, Map.of("success", false, "error", e.getMessage()));
- }
- }
-
- private void handleLogs(HttpExchange exchange) {
- sendJson(exchange, Map.of("success", true, "data", logBuffer.toString()));
- }
-
- private void handleInstanceLogs(HttpExchange exchange) {
- try {
- String query = exchange.getRequestURI().getQuery();
- String instanceName = null;
- if (query != null && query.startsWith("name=")) {
- instanceName = query.substring(5);
- }
-
- if (instanceName == null) {
- sendJson(exchange, Map.of("success", false, "error", "Укажите имя сборки"));
- return;
- }
-
- Path instanceDir = me.sashegdev.zernmc.launcher.utils.Config.getInstancesDir().resolve(instanceName);
- Path logsDir = instanceDir.resolve("logs");
-
- if (!Files.exists(logsDir)) {
- sendJson(exchange, Map.of("success", true, "data", ""));
- return;
- }
-
- StringBuilder logs = new StringBuilder();
- try (var stream = Files.list(logsDir)) {
- stream.filter(f -> f.toString().endsWith(".log"))
- .sorted((a, b) -> b.compareTo(a))
- .limit(5)
- .forEach(logFile -> {
- try {
- logs.append("=== ").append(logFile.getFileName()).append(" ===\n");
- logs.append(Files.readString(logFile));
- logs.append("\n");
- } catch (Exception ignored) {}
- });
- }
-
- sendJson(exchange, Map.of("success", true, "data", logs.toString()));
- } catch (Exception e) {
- sendJson(exchange, Map.of("success", false, "error", e.getMessage()));
- }
- }
-
- private void handleExit(HttpExchange exchange) {
- log("Выход...");
- if (mainStage != null) mainStage.close();
- System.exit(0);
- }
-
- private void handleStatic(HttpExchange exchange) {
- try {
- String path = exchange.getRequestURI().getPath();
- if (path.equals("/ui/") || path.equals("/ui")) path = "/ui/index.html";
-
- var resource = JFXLauncher.class.getResource(path);
- if (resource == null) {
- exchange.sendResponseHeaders(404, 0);
- exchange.close();
- return;
- }
-
- byte[] content = resource.openStream().readAllBytes();
- String ct = getContentType(path);
-
- exchange.getResponseHeaders().set("Content-Type", ct);
- exchange.sendResponseHeaders(200, content.length);
- exchange.getResponseBody().write(content);
- exchange.close();
- } catch (Exception ignored) {}
- }
-
- private String getContentType(String path) {
- if (path.endsWith(".html")) return "text/html; charset=utf-8";
- if (path.endsWith(".css")) return "text/css; charset=utf-8";
- if (path.endsWith(".js")) return "application/javascript; charset=utf-8";
- return "text/plain";
- }
-
- @SuppressWarnings("unchecked")
- private Map parseJson(InputStream body) {
- try {
- return gson.fromJson(new String(body.readAllBytes(), StandardCharsets.UTF_8), Map.class);
- } catch (Exception e) {
- return new HashMap<>();
- }
- }
-
- private void sendJson(HttpExchange exchange, Map response) {
- try {
- String json = gson.toJson(response);
- byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
- exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
- exchange.sendResponseHeaders(200, bytes.length);
- exchange.getResponseBody().write(bytes);
- exchange.close();
- } catch (Exception ignored) {}
- }
-
- private void log(String msg) {
- String entry = "[" + java.time.LocalTime.now() + "] " + msg + "\n";
- logBuffer.append(entry);
- System.out.println("[JFX] " + msg);
- }
-}
\ No newline at end of file
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Version.java b/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Version.java
deleted file mode 100644
index e72abab..0000000
--- a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Version.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package me.sashegdev.zernmc.launcher.utils;
-
-import java.util.jar.Attributes;
-import java.util.jar.Manifest;
-
-public class Version {
-
- public static String getCurrentVersion() {
- try {
- // Способ 1: Из манифеста (самый правильный)
- Manifest manifest = new Manifest(
- Version.class.getClassLoader().getResourceAsStream("META-INF/MANIFEST.MF")
- );
-
- String version = manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION);
- if (version != null && !version.isBlank()) {
- return version;
- }
-
- // Способ 2: Из Package (запасной)
- version = Version.class.getPackage().getImplementationVersion();
- if (version != null && !version.isBlank()) {
- return version;
- }
-
- } catch (Exception ignored) {
- // если не получилось прочитать манифест — идём дальше
- }
-
- // Финальный fallback
- return "1.0.0";
- }
-
- public static boolean isNewer(String current, String server) {
- if (current == null || server == null) return false;
-
- current = current.replace("-SNAPSHOT", "").trim();
- server = server.replace("-SNAPSHOT", "").trim();
-
- if (current.equals(server)) return false;
-
- String[] cParts = current.split("\\.");
- String[] sParts = server.split("\\.");
-
- int max = Math.max(cParts.length, sParts.length);
-
- for (int i = 0; i < max; i++) {
- int c = i < cParts.length ? Integer.parseInt(cParts[i]) : 0;
- int s = i < sParts.length ? Integer.parseInt(sParts[i]) : 0;
-
- if (s > c) return true;
- if (s < c) return false;
- }
- return false;
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/Bootstrap.java
similarity index 57%
rename from launcher/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/Bootstrap.java
index a586d21..27a0c5b 100644
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java
+++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/Bootstrap.java
@@ -7,11 +7,12 @@ import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
+import java.util.List;
public class Bootstrap {
private static final String VERSION_FILE = "build.version";
- private static final String JAR_NAME = "bin/ZernMCLauncher.jar";
- private static final String BASE_URL = "http://87.120.187.36:1582/launcher/download?type=jar";
+ private static final String JAR_NAME = "ZernMCLauncher.jar";
+ private static final String BASE_URL = "http://87.120.187.36:1582";
private static Path baseDir;
private static Path logDir;
@@ -23,26 +24,31 @@ public class Bootstrap {
log("=== ZernMC Launcher ===");
- // Если передан аргумент --launcher, запускаем UI напрямую
- if (args.length > 0 && args[0].equals("--launcher")) {
- launchUI();
- return;
- }
+ // Определяем режим запуска
+ List argList = Arrays.asList(args);
+ boolean cliMode = argList.contains("--cli");
+ boolean jfxMode = !cliMode; // по умолчанию JFX
+ // Проверка и обновление лаунчера
String currentVersion = readCurrentVersion();
String serverVersion = getServerVersion();
- log("Локальная: " + currentVersion);
- log("Сервер: " + serverVersion);
+ log("Локальная версия: " + currentVersion);
+ log("Версия на сервере: " + serverVersion);
if (isNewer(serverVersion, currentVersion)) {
log("Доступно обновление!");
- downloadUpdate();
+ downloadUpdate(serverVersion);
} else {
- log("Актуально");
+ log("Версия актуальна");
}
- launchGame();
+ // Запуск в выбранном режиме
+ if (jfxMode) {
+ launchJFX();
+ } else {
+ launchCLI();
+ }
}
private static void log(String msg) {
@@ -94,13 +100,15 @@ public class Bootstrap {
return false;
}
- private static void downloadUpdate() throws Exception {
- URL url = new URL(BASE_URL);
+ private static void downloadUpdate(String newVersion) throws Exception {
+ URL url = new URL(BASE_URL + "/launcher/download/jar");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
if (conn.getResponseCode() == 200) {
- Path tmp = baseDir.resolve(JAR_NAME + ".new");
+ Path jarFile = baseDir.resolve(JAR_NAME);
+ Path tmp = jarFile.resolveSibling("zernmc-launcher-new.jar");
+
try (InputStream in = conn.getInputStream();
OutputStream out = new FileOutputStream(tmp.toFile())) {
byte[] buf = new byte[8192];
@@ -114,71 +122,94 @@ public class Bootstrap {
}
log("Скачано");
- Path jarFile = baseDir.resolve(JAR_NAME);
- Path backup = baseDir.resolve(JAR_NAME + ".old");
+ Path backup = jarFile.resolveSibling(JAR_NAME + ".old");
- if (Files.exists(jarFile)) Files.move(jarFile, backup);
- Files.move(tmp, jarFile);
+ if (Files.exists(jarFile)) Files.move(jarFile, backup, StandardCopyOption.REPLACE_EXISTING);
+ Files.move(tmp, jarFile, StandardCopyOption.REPLACE_EXISTING);
if (Files.exists(backup)) Files.delete(backup);
- String newVersion = getServerVersion();
Files.writeString(baseDir.resolve(VERSION_FILE), newVersion);
log("Обновлено до v" + newVersion);
+ } else {
+ throw new IOException("Сервер вернул код: " + conn.getResponseCode());
}
}
- private static void launchUI() throws Exception {
- // Запускаем Main с CLI аргументом
- me.sashegdev.zernmc.launcher.Main.main(new String[]{"--cli"});
- }
-
- private static void launchGame() throws Exception {
- // Сначала ищем в lib/jre21-custom/bin/java
- Path javaBin = baseDir.resolve("lib").resolve("jre21-custom").resolve("bin").resolve("java");
- if (System.getProperty("os.name").toLowerCase().contains("windows")) {
- javaBin = javaBin.resolveSibling("java.exe");
- }
-
- // Потом в bin/ (альтернатива)
- if (!Files.exists(javaBin)) {
- javaBin = baseDir.resolve("bin").resolve("java");
- if (System.getProperty("os.name").toLowerCase().contains("windows")) {
- javaBin = javaBin.resolveSibling("java.exe");
- }
- }
-
- // Системная java как запасной вариант
- if (!Files.exists(javaBin)) {
- javaBin = Paths.get(System.getProperty("java.home"), "bin", "java");
- if (System.getProperty("os.name").toLowerCase().contains("windows")) {
- javaBin = javaBin.resolveSibling("java.exe");
- }
- }
-
- log("Java: " + javaBin);
- log("baseDir: " + baseDir);
- log("exists baseDir: " + Files.exists(baseDir));
- log("baseDir list: " + Arrays.toString(baseDir.toFile().list()));
- log("exists java: " + Files.exists(javaBin));
- log("Запуск...");
-
+ private static void launchJFX() throws Exception {
+ Path javaBin = findJava();
Path jarPath = baseDir.resolve(JAR_NAME);
- log("jarPath: " + jarPath);
- log("jarPath exists: " + Files.exists(jarPath));
- log("jarPath abs: " + jarPath.toAbsolutePath());
- log("jarPath str: " + jarPath.toAbsolutePath().toString());
+
+ log("Запуск JFX режима...");
+ log("Java: " + javaBin);
+ log("JAR: " + jarPath);
ProcessBuilder pb = new ProcessBuilder(
javaBin.toAbsolutePath().toString(),
"-jar",
jarPath.toAbsolutePath().toString(),
- "--launcher"
+ "--jfx"
);
pb.directory(baseDir.toFile());
pb.inheritIO();
Process p = pb.start();
int code = p.waitFor();
- log("Завершено: " + code);
+ log("Завершено с кодом: " + code);
System.exit(code);
}
+
+ private static void launchCLI() throws Exception {
+ Path javaBin = findJava();
+ Path jarPath = baseDir.resolve(JAR_NAME);
+
+ log("Запуск CLI режима...");
+ log("Java: " + javaBin);
+ log("JAR: " + jarPath);
+
+ ProcessBuilder pb = new ProcessBuilder(
+ javaBin.toAbsolutePath().toString(),
+ "-jar",
+ jarPath.toAbsolutePath().toString(),
+ "--cli"
+ );
+ pb.directory(baseDir.toFile());
+ pb.inheritIO();
+ Process p = pb.start();
+ int code = p.waitFor();
+ log("Завершено с кодом: " + code);
+ System.exit(code);
+ }
+
+ private static Path findJava() {
+ String os = System.getProperty("os.name").toLowerCase();
+ String javaExe = os.contains("windows") ? "java.exe" : "java";
+
+ // Сначала ищем jre21/bin/java рядом с лаунчером
+ Path javaBin = baseDir.resolve("jre21").resolve("bin").resolve(javaExe);
+
+ // Если нет, пробуем системную Java
+ if (!Files.exists(javaBin)) {
+ javaBin = Paths.get(System.getProperty("java.home"), "bin", javaExe);
+ }
+
+ // Если и это не найдено - ищем java в PATH
+ if (!Files.exists(javaBin)) {
+ try {
+ Process p = new ProcessBuilder("which", javaExe).start();
+ if (p.waitFor() == 0) {
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
+ String path = br.readLine();
+ if (path != null) {
+ javaBin = Paths.get(path.trim());
+ }
+ }
+ }
+ } catch (Exception ignored) {}
+ }
+
+ if (!Files.exists(javaBin)) {
+ throw new RuntimeException("Java не найдена. Убедитесь, что jre21 присутствует в папке с лаунчером или Java установлена в системе");
+ }
+
+ return javaBin;
+ }
}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/Main.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/Main.java
similarity index 58%
rename from launcher/src/main/java/me/sashegdev/zernmc/launcher/Main.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/Main.java
index 3d94a7e..c7ed1e3 100644
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/Main.java
+++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/Main.java
@@ -4,14 +4,9 @@ import me.sashegdev.zernmc.launcher.api.LauncherAPI;
import me.sashegdev.zernmc.launcher.auth.AuthManager;
import me.sashegdev.zernmc.launcher.menu.*;
import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
+import me.sashegdev.zernmc.launcher.ui.jfx.JFXLauncher;
import me.sashegdev.zernmc.launcher.utils.*;
import java.io.IOException;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
import java.util.List;
public class Main {
@@ -20,20 +15,60 @@ public class Main {
private static final LauncherAPI api = new LauncherAPI();
public static void main(String[] args) throws IOException {
+ // Настройка кодировки для Windows и Linux
System.setProperty("org.jline.terminal.disableDeprecatedProviderWarning", "true");
System.setProperty("file.encoding", "UTF-8");
System.setProperty("sun.err.encoding", "UTF-8");
System.setProperty("sun.stdout.encoding", "UTF-8");
+
+ // Для Windows CMD - пытаемся переключить в UTF-8 режим
+ try {
+ if (System.getProperty("os.name").toLowerCase().contains("windows")) {
+ new ProcessBuilder("cmd", "/c", "chcp", "65001").inheritIO().start().waitFor();
+ }
+ } catch (Exception ignored) {}
ZAnsi.install();
System.out.print("\033[H\033[2J");
System.out.println(ZAnsi.brightGreen("Добро пожаловать в ZernMC Launcher " + CURRENT_VERSION));
+ // Определяем режим запуска
+ List argList = List.of(args);
+ boolean jfxMode = argList.contains("--jfx");
+ boolean cliMode = argList.contains("--cli");
+
+ if (jfxMode) {
+ launchJFX();
+ return;
+ }
+
+ // CLI режим (по умолчанию или с --cli)
+ startCLI();
+ }
+
+ private static void launchJFX() {
+ System.out.println(ZAnsi.cyan("Запуск JFX интерфейса..."));
+ try {
+ // Устанавливаем параметры для JavaFX (важно для Windows)
+ System.setProperty("javafx.runtime.version", "21");
+
+ JFXLauncher.main(new String[]{});
+ } catch (Exception e) {
+ System.err.println(ZAnsi.brightRed("Ошибка запуска JFX: " + e.getMessage()));
+ // Проверяем, связано ли это с отсутствием JavaFX
+ if (e.getMessage() != null && e.getMessage().contains("QuantumRenderer")) {
+ System.err.println(ZAnsi.yellow("JavaFX недоступен. Возможно, отсутствуют нативные библиотеки."));
+ System.err.println(ZAnsi.yellow("Попробуйте использовать CLI режим: --cli"));
+ }
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void startCLI() throws IOException {
// Проверка всех сервисов при старте
ZHttpClient.checkAllServicesOnStartup();
- checkAndAutoUpdateLauncher();
-
// === АВТОРИЗАЦИЯ (используем новый API) ===
System.out.println(ZAnsi.cyan("Проверка авторизации..."));
var sessionResponse = api.checkSession();
@@ -62,92 +97,6 @@ public class Main {
}
}
- private static void checkAndAutoUpdateLauncher() {
- System.out.println(ZAnsi.cyan("Проверка обновлений лаунчера..."));
- try {
- String json = ZHttpClient.getLauncherVersionInfo();
- String serverVersion = extractVersion(json);
-
- System.out.println(ZAnsi.white("Текущая версия: ") + CURRENT_VERSION);
- System.out.println(ZAnsi.white("Версия на сервере: ") + serverVersion);
-
- if (Version.isNewer(CURRENT_VERSION, serverVersion)) {
- System.out.println(ZAnsi.brightYellow("\nДоступна новая версия лаунчера! (" + serverVersion + ")"));
- System.out.println(ZAnsi.cyan("Начинается автоматическое обновление...\n"));
- performAutoUpdate(serverVersion);
- restartLauncher();
- } else {
- System.out.println(ZAnsi.brightGreen("Лаунчер актуален."));
- }
- } catch (Exception e) {
- System.out.println(ZAnsi.yellow("Не удалось проверить обновления лаунчера."));
- System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
- }
- }
-
- private static void performAutoUpdate(String newVersion) throws Exception {
- String downloadUrl = ZHttpClient.getBaseUrl() + "/launcher/download?type=jar";
- Path currentJar = getCurrentJarPath();
- Path tempJar = currentJar.getParent().resolve("zernmc-launcher-new.jar");
-
- System.out.println(ZAnsi.cyan("Скачивание версии " + newVersion + "..."));
-
- HttpClient client = HttpClient.newBuilder().build();
- HttpRequest request = HttpRequest.newBuilder()
- .uri(java.net.URI.create(downloadUrl))
- .GET()
- .build();
-
- HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofFile(tempJar));
-
- if (response.statusCode() != 200) {
- throw new IOException("Сервер вернул код: " + response.statusCode());
- }
-
- long size = Files.size(tempJar);
- System.out.println(ZAnsi.brightGreen("Скачано успешно (" + (size / 1024) + " KB)"));
-
- Files.move(tempJar, currentJar, StandardCopyOption.REPLACE_EXISTING);
- System.out.println(ZAnsi.brightGreen("Обновление успешно установлено!"));
- }
-
- private static void restartLauncher() {
- try {
- String javaPath = System.getProperty("java.home") + "/bin/java";
- String jarPath = getCurrentJarPath().toAbsolutePath().toString();
-
- System.out.println(ZAnsi.brightGreen("Перезапуск лаунчера с новой версией..."));
-
- new ProcessBuilder(javaPath, "-jar", jarPath)
- .inheritIO()
- .start();
-
- System.exit(0);
- } catch (Exception e) {
- System.err.println(ZAnsi.brightRed("Не удалось перезапустить лаунчер."));
- System.exit(1);
- }
- }
-
- private static String extractVersion(String json) {
- try {
- return json.replaceAll(".*\"version\"\\s*:\\s*\"([^\"]+)\".*", "$1");
- } catch (Exception e) {
- return "unknown";
- }
- }
-
- private static Path getCurrentJarPath() {
- try {
- return Path.of(Main.class.getProtectionDomain()
- .getCodeSource()
- .getLocation()
- .toURI());
- } catch (Exception e) {
- return Path.of("zernmc-launcher-1.0-jar-with-dependencies.jar");
- }
- }
-
// ====================== ГЛАВНЫЙ ЦИКЛ ======================
private static void mainLoop() throws Exception {
if (Config.isZernMCBuild()) {
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/ApiResponse.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/ApiResponse.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/ApiResponse.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/ApiResponse.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/LauncherAPI.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/LauncherAPI.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/LauncherAPI.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/LauncherAPI.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/auth/AuthService.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/auth/AuthService.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/auth/AuthService.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/auth/AuthService.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/instance/InstanceService.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/instance/InstanceService.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/instance/InstanceService.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/instance/InstanceService.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/launch/LaunchService.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/launch/LaunchService.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/launch/LaunchService.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/api/launch/LaunchService.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/auth/AuthManager.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/auth/AuthManager.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/auth/AuthManager.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/auth/AuthManager.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/LaunchMenu.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/menu/LaunchMenu.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/LaunchMenu.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/menu/LaunchMenu.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/LoginMenu.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/menu/LoginMenu.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/LoginMenu.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/menu/LoginMenu.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/ServerCheckMenu.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/menu/ServerCheckMenu.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/ServerCheckMenu.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/menu/ServerCheckMenu.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/SettingsMenu.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/menu/SettingsMenu.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/SettingsMenu.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/menu/SettingsMenu.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/UpdateMenu.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/menu/UpdateMenu.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/UpdateMenu.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/menu/UpdateMenu.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/Instance.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/Instance.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/Instance.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/Instance.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/InstanceManager.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/InstanceManager.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/InstanceManager.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/InstanceManager.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/MinecraftLib.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/MinecraftLib.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/MinecraftLib.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/MinecraftLib.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/PackDownloader.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/PackDownloader.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/PackDownloader.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/PackDownloader.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/ServerPack.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/ServerPack.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/ServerPack.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/ServerPack.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/FabricInstaller.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/FabricInstaller.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/FabricInstaller.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/FabricInstaller.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/NeoForgeInstaller.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/NeoForgeInstaller.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/NeoForgeInstaller.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/NeoForgeInstaller.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/launch/LaunchCommandBuilder.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/launch/LaunchCommandBuilder.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/launch/LaunchCommandBuilder.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/launch/LaunchCommandBuilder.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/launch/VersionManifest.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/launch/VersionManifest.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/launch/VersionManifest.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/launch/VersionManifest.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/model/LaunchOptions.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/model/LaunchOptions.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/model/LaunchOptions.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/model/LaunchOptions.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/model/MinecraftVersion.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/model/MinecraftVersion.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/model/MinecraftVersion.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/model/MinecraftVersion.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/ArrowMenu.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/ArrowMenu.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/ArrowMenu.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/ArrowMenu.java
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java
similarity index 100%
rename from launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Config.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/Config.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Config.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/Config.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ConsoleUtils.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/ConsoleUtils.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ConsoleUtils.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/ConsoleUtils.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Input.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/Input.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Input.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/Input.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ProgressBar.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/ProgressBar.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ProgressBar.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/ProgressBar.java
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Version.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/Version.java
similarity index 59%
rename from launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Version.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/Version.java
index e72abab..eebf168 100644
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Version.java
+++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/Version.java
@@ -33,24 +33,42 @@ public class Version {
public static boolean isNewer(String current, String server) {
if (current == null || server == null) return false;
-
- current = current.replace("-SNAPSHOT", "").trim();
- server = server.replace("-SNAPSHOT", "").trim();
-
+
+ // Нормализуем версии - убираем суффиксы типа -any, -alpha, -beta, -SNAPSHOT
+ current = normalizeVersion(current);
+ server = normalizeVersion(server);
+
if (current.equals(server)) return false;
-
+
String[] cParts = current.split("\\.");
String[] sParts = server.split("\\.");
-
+
int max = Math.max(cParts.length, sParts.length);
-
+
for (int i = 0; i < max; i++) {
- int c = i < cParts.length ? Integer.parseInt(cParts[i]) : 0;
- int s = i < sParts.length ? Integer.parseInt(sParts[i]) : 0;
-
+ int c = i < cParts.length ? parseVersionPart(cParts[i]) : 0;
+ int s = i < sParts.length ? parseVersionPart(sParts[i]) : 0;
+
if (s > c) return true;
if (s < c) return false;
}
return false;
}
+
+ private static String normalizeVersion(String version) {
+ if (version == null) return "0.0.0";
+
+ // Убираем суффиксы: -any, -alpha1, -beta2, -SNAPSHOT, -rc1 и т.д.
+ return version.split("-")[0].split("\\+")[0].trim();
+ }
+
+ private static int parseVersionPart(String part) {
+ try {
+ // Убираем всё, что не является цифрой (на случай если суффикс остался)
+ String numeric = part.replaceAll("[^0-9]", "");
+ return numeric.isEmpty() ? 0 : Integer.parseInt(numeric);
+ } catch (Exception e) {
+ return 0;
+ }
+ }
}
\ No newline at end of file
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ZAnsi.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/ZAnsi.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ZAnsi.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/ZAnsi.java
diff --git a/launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ZHttpClient.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/ZHttpClient.java
similarity index 100%
rename from launcher/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ZHttpClient.java
rename to launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/ZHttpClient.java
diff --git a/launcher/launcher/src/main/resources/ui/index.html b/launcher/launcher/src/main/resources/ui/index.html
deleted file mode 100644
index c1fa627..0000000
--- a/launcher/launcher/src/main/resources/ui/index.html
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
-
- ZernMC Launcher
-
-
-
-
-
-
-
-
-
-
-
ZernMC
-
Private Launcher
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/launcher/launcher/src/main/resources/ui/launcher.js b/launcher/launcher/src/main/resources/ui/launcher.js
deleted file mode 100644
index 68ce339..0000000
--- a/launcher/launcher/src/main/resources/ui/launcher.js
+++ /dev/null
@@ -1,393 +0,0 @@
-const API_BASE = 'http://localhost:8080/api';
-
-let state = {
- loggedIn: false,
- account: null,
- instances: [],
- selectedInstance: null
-};
-
-// ============ API ============
-
-async function apiCall(endpoint, options = {}) {
- const url = `${API_BASE}${endpoint}`;
- const config = {
- headers: { 'Content-Type': 'application/json' },
- ...options
- };
-
- try {
- const response = await fetch(url, config);
- const data = await response.json();
- return data;
- } catch (e) {
- log('Ошибка соединения с сервером: ' + e.message, 'error');
- return { success: false, error: e.message };
- }
-}
-
-// ============ Login ============
-
-async function login(username, password) {
- log('Выполняется вход...', 'info');
- const result = await apiCall('/login', {
- method: 'POST',
- body: JSON.stringify({ username, password })
- });
-
- if (result.success) {
- state.loggedIn = true;
- state.account = result.data;
- log('Вход выполнен: ' + result.data.username, 'success');
- showMainScreen();
- await loadInstances();
- } else {
- log('Ошибка входа: ' + result.error, 'error');
- showError(result.error);
- }
- return result;
-}
-
-function showError(message) {
- const el = document.getElementById('login-error');
- el.textContent = message;
- el.classList.remove('hidden');
-}
-
-function hideError() {
- document.getElementById('login-error').classList.add('hidden');
-}
-
-// ============ Account ============
-
-async function loadAccountInfo() {
- const result = await apiCall('/account');
- if (result.success) {
- state.account = result.data;
- state.loggedIn = true;
- document.getElementById('account-name').textContent = result.data.username;
-
- const statusEl = document.getElementById('account-status');
- statusEl.textContent = result.data.passActive ? 'PRO' : 'FREE';
- statusEl.className = 'badge ' + (result.data.passActive ? 'active' : 'inactive');
- } else {
- showLoginScreen();
- }
-}
-
-// ============ Instances ============
-
-async function loadInstances() {
- log('Загрузка списка сборок...', 'info');
- const result = await apiCall('/instances');
-
- if (result.success) {
- state.instances = result.data;
- renderInstances();
- log('Загружено ' + result.data.length + ' сборок', 'success');
- } else {
- log('Ошибка загрузки: ' + result.error, 'error');
- }
-}
-
-function renderInstances() {
- const container = document.getElementById('instances-list');
- container.innerHTML = '';
-
- state.instances.forEach(inst => {
- const card = document.createElement('div');
- card.className = 'instance-card';
- card.dataset.name = inst.name;
- card.onclick = () => selectInstance(inst.name);
-
- let details = `
- ${inst.version || '?'}
- ${inst.loaderType || 'vanilla'}
- `;
-
- if (inst.isServerPack) {
- details += `v${inst.serverVersion}`;
- }
-
- card.innerHTML = `
- ${inst.name}
- ${details}
- `;
-
- container.appendChild(card);
- });
-}
-
-function selectInstance(name) {
- state.selectedInstance = state.instances.find(i => i.name === name);
-
- document.querySelectorAll('.instance-card').forEach(c => {
- c.classList.toggle('selected', c.dataset.name === name);
- });
-
- const btn = document.getElementById('play-btn');
- const inst = state.selectedInstance;
-
- if (inst) {
- document.getElementById('selected-name').textContent = inst.name;
- document.getElementById('selected-version').textContent = inst.version || '-';
- document.getElementById('selected-loader').textContent = inst.loaderType || 'vanilla';
-
- btn.disabled = false;
- btn.textContent = 'Играть';
- btn.classList.remove('update');
-
- loadInstanceLogs(inst.name);
- } else {
- btn.disabled = true;
- btn.textContent = 'Выберите сборку';
- }
-}
-
-// ============ Launch ============
-
-async function launchInstance() {
- if (!state.selectedInstance) return;
-
- const name = state.selectedInstance.name;
- log('Запуск сборки: ' + name, 'info');
-
- const result = await apiCall('/launch', {
- method: 'POST',
- body: JSON.stringify({ name })
- });
-
- if (result.success) {
- log('Сборка запущена! PID: ' + result.data.pid, 'success');
- } else {
- log('Ошибка запуска: ' + result.error, 'error');
- }
-}
-
-// ============ Install ============
-
-function openInstallModal() {
- document.getElementById('install-modal').classList.remove('hidden');
-}
-
-function closeInstallModal() {
- document.getElementById('install-modal').classList.add('hidden');
-}
-
-async function installInstance(formData) {
- log('Установка сборки...', 'info');
- const result = await apiCall('/install', {
- method: 'POST',
- body: JSON.stringify(formData)
- });
-
- if (result.success) {
- log('Сборка установлена!', 'success');
- closeInstallModal();
- await loadInstances();
- } else {
- log('Ошибка установки: ' + result.error, 'error');
- }
- return result;
-}
-
-// ============ Logs ============
-
-function log(message, type = 'info') {
- const container = document.getElementById('logs-container');
- if (!container) return;
-
- const line = document.createElement('div');
- line.className = 'log-line ' + type;
- line.textContent = '[' + new Date().toLocaleTimeString() + '] ' + message;
- container.appendChild(line);
- container.scrollTop = container.scrollHeight;
-}
-
-async function loadInstanceLogs(instanceName) {
- const result = await apiCall('/logs/instance?name=' + encodeURIComponent(instanceName));
- if (result.success && result.data) {
- result.data.split('\n').forEach(line => {
- if (line.trim()) {
- let type = 'info';
- if (line.toLowerCase().includes('error')) type = 'error';
- else if (line.toLowerCase().includes('warn')) type = 'warning';
- else if (line.toLowerCase().includes('info')) type = 'success';
- log(line, type);
- }
- });
- }
-}
-
-function clearLogs() {
- document.getElementById('logs-container').innerHTML = '';
-}
-
-// ============ Screens ============
-
-function showLoginScreen() {
- document.getElementById('login-screen').classList.remove('hidden');
- document.getElementById('main-screen').classList.add('hidden');
- clearError();
-}
-
-function showMainScreen() {
- document.getElementById('login-screen').classList.add('hidden');
- document.getElementById('main-screen').classList.remove('hidden');
-
- if (state.account) {
- document.getElementById('account-name').textContent = state.account.username;
- const statusEl = document.getElementById('account-status');
- statusEl.textContent = state.account.passActive ? 'PRO' : 'FREE';
- statusEl.className = 'badge ' + (state.account.passActive ? 'active' : 'inactive');
- }
-}
-
-// ============ Init ============
-
-document.addEventListener('DOMContentLoaded', async () => {
- initGridBackground();
- log('Запуск лаунчера...', 'info');
-
- await loadAccountInfo();
-
- if (!state.loggedIn) {
- showLoginScreen();
- } else {
- showMainScreen();
- await loadInstances();
- }
-});
-
-// ============ Form Handlers ============
-
-document.getElementById('login-form').addEventListener('submit', async (e) => {
- e.preventDefault();
- hideError();
-
- const username = document.getElementById('username').value;
- const password = document.getElementById('password').value;
-
- await login(username, password);
-});
-
-document.getElementById('play-btn').addEventListener('click', async () => {
- await launchInstance();
-});
-
-document.getElementById('install-form').addEventListener('submit', async (e) => {
- e.preventDefault();
-
- const formData = {
- name: document.getElementById('install-name').value,
- version: document.getElementById('install-mc-version').value,
- loader: document.getElementById('install-loader').value
- };
-
- await installInstance(formData);
-});
-
-// Expose functions globally for inline handlers
-window.closeInstallModal = closeInstallModal;
-
-// ============ Grid Background ============
-
-function initGridBackground() {
- const canvas = document.getElementById('grid-canvas');
- if (!canvas) return;
-
- const ctx = canvas.getContext('2d');
- let width, height;
- let mouseX = 0, mouseY = 0;
- let points = [];
- const spacing = 40;
- const depthLayers = 3;
-
- function resize() {
- width = canvas.width = window.innerWidth;
- height = canvas.height = window.innerHeight;
- initPoints();
- }
-
- function initPoints() {
- points = [];
- for (let z = 0; z < depthLayers; z++) {
- const layer = [];
- const scale = 0.6 + z * 0.2;
- const cols = Math.ceil(width / spacing / scale) + 1;
- const rows = Math.ceil(height / spacing / scale) + 1;
-
- for (let y = 0; y < rows; y++) {
- for (let x = 0; x < cols; x++) {
- layer.push({
- x: x * spacing * scale,
- y: y * spacing * scale,
- baseX: x * spacing * scale,
- baseY: y * spacing * scale,
- depth: z
- });
- }
- }
- points.push(layer);
- }
- }
-
- function draw() {
- ctx.clearRect(0, 0, width, height);
-
- const centerX = width / 2;
- const centerY = height / 2;
-
- for (let z = 0; z < depthLayers; z++) {
- const layer = points[z];
- const alpha = 0.15 + z * 0.1;
- const scale = 0.6 + z * 0.2;
-
- ctx.strokeStyle = z === depthLayers - 1
- ? `rgba(59, 130, 246, ${alpha})` // Blue - primary
- : `rgba(245, 158, 11, ${alpha * 0.5})`; // Orange - secondary
-
- ctx.lineWidth = 1;
-
- for (const p of layer) {
- const dx = (mouseX - centerX) * 0.02 * scale * (z + 1);
- const dy = (mouseY - centerY) * 0.02 * scale * (z + 1);
-
- const px = p.baseX + dx;
- const py = p.baseY + dy;
-
- // Horizontal line
- if ((p.baseX + dx) > 0 && (p.baseX + dx) < width - spacing * scale) {
- ctx.beginPath();
- ctx.moveTo(px, py);
- ctx.lineTo(px + spacing * scale, py);
- ctx.stroke();
- }
-
- // Vertical line
- if ((p.baseY + dy) > 0 && (p.baseY + dy) < height - spacing * scale) {
- ctx.beginPath();
- ctx.moveTo(px, py);
- ctx.lineTo(px, py + spacing * scale);
- ctx.stroke();
- }
- }
- }
-
- requestAnimationFrame(draw);
- }
-
- canvas.addEventListener('mousemove', (e) => {
- mouseX = e.clientX;
- mouseY = e.clientY;
- });
-
- canvas.addEventListener('mouseleave', () => {
- mouseX = width / 2;
- mouseY = height / 2;
- });
-
- window.addEventListener('resize', resize);
- resize();
- draw();
-}
\ No newline at end of file
diff --git a/launcher/launcher/src/main/resources/ui/style.css b/launcher/launcher/src/main/resources/ui/style.css
deleted file mode 100644
index 1d38d20..0000000
--- a/launcher/launcher/src/main/resources/ui/style.css
+++ /dev/null
@@ -1,447 +0,0 @@
-:root {
- --bg-primary: #0f172a;
- --bg-secondary: #1e293b;
- --bg-tertiary: #334155;
- --accent: #f59e0b;
- --accent-secondary: #3b82f6;
- --accent-hover: #fbbf24;
- --text-primary: #f1f5f9;
- --text-secondary: #94a3b8;
- --success: #22c55e;
- --warning: #f59e0b;
- --error: #ef4444;
- --border: #475569;
- --shadow: rgba(0, 0, 0, 0.4);
-}
-
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
-}
-
-body {
- font-family: 'Segoe UI', system-ui, sans-serif;
- background: var(--bg-primary);
- color: var(--text-primary);
- height: 100vh;
- overflow: hidden;
-}
-
-#app {
- height: 100vh;
- display: flex;
- flex-direction: column;
- position: relative;
- z-index: 1;
-}
-
-#grid-canvas {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 0;
- pointer-events: none;
-}
-
-/* Screens */
-.screen {
- position: absolute;
- inset: 0;
- display: flex;
- flex-direction: column;
-}
-
-.hidden {
- display: none !important;
-}
-
-/* Login Screen */
-#login-screen {
- justify-content: center;
- align-items: center;
- background: transparent;
-}
-
-.login-container {
- background: rgba(30, 41, 59, 0.95);
- backdrop-filter: blur(10px);
- padding: 3rem;
- border-radius: 16px;
- box-shadow: 0 25px 50px var(--shadow);
- width: 100%;
- max-width: 400px;
- border: 1px solid var(--border);
-}
-
-.logo {
- font-size: 2.5rem;
- text-align: center;
- color: var(--accent);
- margin-bottom: 0.5rem;
-}
-
-.subtitle {
- text-align: center;
- color: var(--text-secondary);
- margin-bottom: 2rem;
-}
-
-#login-form {
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-input, select {
- background: var(--bg-primary);
- border: 1px solid var(--border);
- color: var(--text-primary);
- padding: 0.875rem 1rem;
- border-radius: 8px;
- font-size: 1rem;
- transition: border-color 0.2s;
-}
-
-input:focus, select:focus {
- outline: none;
- border-color: var(--accent);
-}
-
-input::placeholder {
- color: var(--text-secondary);
-}
-
-.btn-primary {
- background: var(--accent);
- color: white;
- border: none;
- padding: 0.875rem 1rem;
- border-radius: 8px;
- font-size: 1rem;
- font-weight: 600;
- cursor: pointer;
- transition: background 0.2s;
-}
-
-.btn-primary:hover {
- background: var(--accent-hover);
-}
-
-.btn-primary:disabled {
- background: var(--text-secondary);
- cursor: not-allowed;
-}
-
-.btn-secondary {
- background: var(--bg-tertiary);
- color: var(--text-primary);
- border: 1px solid var(--border);
- padding: 0.875rem 1rem;
- border-radius: 8px;
- font-size: 1rem;
- cursor: pointer;
- transition: background 0.2s;
-}
-
-.btn-secondary:hover {
- background: var(--bg-secondary);
-}
-
-.error {
- color: var(--error);
- text-align: center;
- margin-top: 1rem;
- padding: 0.75rem;
- border-radius: 8px;
- background: rgba(239, 68, 68, 0.1);
-}
-
-/* Main Screen */
-#main-screen {
- display: flex;
- flex-direction: column;
- height: 100vh;
-}
-
-/* Header */
-.header {
- background: var(--bg-secondary);
- padding: 1rem 1.5rem;
- display: flex;
- justify-content: space-between;
- align-items: center;
- border-bottom: 1px solid var(--border);
-}
-
-.header .logo {
- font-size: 1.5rem;
- margin: 0;
-}
-
-.account-info {
- display: flex;
- align-items: center;
- gap: 1rem;
-}
-
-#account-name {
- font-weight: 600;
-}
-
-.badge {
- padding: 0.25rem 0.75rem;
- border-radius: 20px;
- font-size: 0.875rem;
- font-weight: 500;
-}
-
-.badge.active {
- background: rgba(74, 222, 128, 0.2);
- color: var(--success);
-}
-
-.badge.inactive {
- background: rgba(239, 68, 68, 0.2);
- color: var(--error);
-}
-
-/* Main Content */
-.main-content {
- flex: 1;
- display: grid;
- grid-template-columns: 280px 1fr;
- gap: 1px;
- background: var(--border);
- overflow: hidden;
-}
-
-/* Sidebar */
-.sidebar {
- background: var(--bg-secondary);
- padding: 1rem;
- overflow-y: auto;
-}
-
-.sidebar h2 {
- font-size: 0.875rem;
- text-transform: uppercase;
- letter-spacing: 0.05em;
- color: var(--text-secondary);
- margin-bottom: 1rem;
-}
-
-.instances-container {
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
-}
-
-.instance-card {
- background: var(--bg-primary);
- border: 1px solid var(--border);
- border-radius: 10px;
- padding: 1rem;
- cursor: pointer;
- transition: all 0.2s;
-}
-
-.instance-card:hover {
- border-color: var(--accent);
- transform: translateY(-2px);
-}
-
-.instance-card.selected {
- border-color: var(--accent);
- background: rgba(233, 69, 96, 0.1);
-}
-
-.instance-name {
- font-weight: 600;
- margin-bottom: 0.5rem;
-}
-
-.instance-details {
- display: flex;
- gap: 0.5rem;
- flex-wrap: wrap;
-}
-
-.instance-version, .instance-loader {
- font-size: 0.75rem;
- padding: 0.25rem 0.5rem;
- border-radius: 4px;
- background: var(--bg-tertiary);
-}
-
-.instance-server-version {
- font-size: 0.75rem;
- padding: 0.25rem 0.5rem;
- border-radius: 4px;
- background: rgba(251, 191, 36, 0.2);
- color: var(--warning);
-}
-
-/* Logs Panel */
-.logs-panel {
- background: var(--bg-primary);
- padding: 1rem;
- display: flex;
- flex-direction: column;
- overflow: hidden;
-}
-
-.logs-panel h2 {
- font-size: 0.875rem;
- text-transform: uppercase;
- letter-spacing: 0.05em;
- color: var(--text-secondary);
- margin-bottom: 1rem;
-}
-
-#logs-container {
- flex: 1;
- background: #0d0d1a;
- border-radius: 8px;
- padding: 1rem;
- font-family: 'Consolas', 'Monaco', monospace;
- font-size: 0.875rem;
- overflow-y: auto;
- line-height: 1.6;
-}
-
-.log-line {
- margin-bottom: 0.25rem;
- white-space: pre-wrap;
- word-break: break-all;
-}
-
-.log-line.info { color: var(--text-primary); }
-.log-line.success { color: var(--success); }
-.log-line.warning { color: var(--warning); }
-.log-line.error { color: var(--error); }
-
-/* Footer */
-.footer {
- background: var(--bg-secondary);
- padding: 1rem 1.5rem;
- display: flex;
- justify-content: space-between;
- align-items: center;
- border-top: 1px solid var(--border);
-}
-
-.instance-info {
- display: flex;
- gap: 1rem;
- align-items: center;
-}
-
-.instance-info span {
- padding: 0.5rem 1rem;
- background: var(--bg-primary);
- border-radius: 6px;
- font-size: 0.875rem;
-}
-
-#selected-name {
- font-weight: 600;
- color: var(--accent);
-}
-
-.btn-play {
- background: var(--success);
- color: #0a0a0a;
- border: none;
- padding: 0.875rem 2rem;
- border-radius: 8px;
- font-size: 1.125rem;
- font-weight: 700;
- cursor: pointer;
- transition: all 0.2s;
- text-transform: uppercase;
- letter-spacing: 0.05em;
-}
-
-.btn-play:hover:not(:disabled) {
- transform: scale(1.05);
- box-shadow: 0 0 20px rgba(74, 222, 128, 0.4);
-}
-
-.btn-play:disabled {
- background: var(--text-secondary);
- cursor: not-allowed;
-}
-
-.btn-play.update {
- background: var(--warning);
-}
-
-/* Modal */
-.modal {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.8);
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 100;
-}
-
-.modal-content {
- background: var(--bg-secondary);
- padding: 2rem;
- border-radius: 16px;
- width: 100%;
- max-width: 450px;
- border: 1px solid var(--border);
-}
-
-.modal-content h2 {
- margin-bottom: 1.5rem;
-}
-
-#install-form {
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-#install-form label {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- font-size: 0.875rem;
- color: var(--text-secondary);
-}
-
-#install-form select, #install-form input {
- width: 100%;
-}
-
-.modal-buttons {
- display: flex;
- gap: 1rem;
- justify-content: flex-end;
- margin-top: 1rem;
-}
-
-/* Scrollbar */
-::-webkit-scrollbar {
- width: 8px;
-}
-
-::-webkit-scrollbar-track {
- background: var(--bg-primary);
-}
-
-::-webkit-scrollbar-thumb {
- background: var(--bg-tertiary);
- border-radius: 4px;
-}
-
-::-webkit-scrollbar-thumb:hover {
- background: var(--border);
-}
\ No newline at end of file
diff --git a/launcher/src/main/resources/ui/index.html b/launcher/launcher/src/resources/ui/index.html
similarity index 100%
rename from launcher/src/main/resources/ui/index.html
rename to launcher/launcher/src/resources/ui/index.html
diff --git a/launcher/src/main/resources/ui/launcher.js b/launcher/launcher/src/resources/ui/launcher.js
similarity index 100%
rename from launcher/src/main/resources/ui/launcher.js
rename to launcher/launcher/src/resources/ui/launcher.js
diff --git a/launcher/src/main/resources/ui/style.css b/launcher/launcher/src/resources/ui/style.css
similarity index 100%
rename from launcher/src/main/resources/ui/style.css
rename to launcher/launcher/src/resources/ui/style.css
diff --git a/launcher/launcher/src/test/java/me/sashegdev/zernmc/launcher/VersionTest.java b/launcher/launcher/src/test/java/me/sashegdev/zernmc/launcher/VersionTest.java
deleted file mode 100644
index 2ad04b0..0000000
--- a/launcher/launcher/src/test/java/me/sashegdev/zernmc/launcher/VersionTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package me.sashegdev.zernmc.launcher;
-
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-
-class VersionTest {
-
- @Test
- void testVersionParsing() {
- assertEquals("1.0.8", parseVersion("{\"version\":\"1.0.8\"}"));
- assertEquals("1.0.2", parseVersion("version:1.0.2"));
- assertEquals("unknown", parseVersion("invalid"));
- assertEquals("2.0.0", parseVersion("\"version\":\"2.0.0\""));
- }
-
- @Test
- void testVersionComparison() {
- assertTrue(isNewer("1.0.8", "1.0.7"));
- assertTrue(isNewer("1.0.8", "1.0.2"));
- assertTrue(isNewer("2.0.0", "1.0.8"));
- assertFalse(isNewer("1.0.8", "1.0.8"));
- assertFalse(isNewer("1.0.7", "1.0.8"));
- assertTrue(isNewer("1.0.10", "1.0.9"));
- }
-
- private String parseVersion(String line) {
- if (line != null && line.contains("version")) {
- int start = line.indexOf("\"version\":\"");
- if (start >= 0) {
- start += 11;
- int end = line.indexOf("\"", start);
- if (end > start) {
- return line.substring(start, end);
- }
- }
- }
- return "unknown";
- }
-
- private boolean isNewer(String server, String current) {
- try {
- String[] sa = server.split("\\.");
- String[] ca = current.split("\\.");
- for (int i = 0; i < Math.min(sa.length, ca.length); i++) {
- int sv = Integer.parseInt(sa[i]);
- int cv = Integer.parseInt(ca[i]);
- if (sv > cv) return true;
- if (sv < cv) return false;
- }
- return sa.length > ca.length;
- } catch (Exception ignored) {}
- return false;
- }
-}
\ No newline at end of file
diff --git a/launcher/pom.xml b/launcher/pom.xml
index 1e9df57..9cfb6e5 100644
--- a/launcher/pom.xml
+++ b/launcher/pom.xml
@@ -7,7 +7,15 @@
me.sashegdev
ZernMCLauncher
1.0.8
- jar
+ pom
+
+ ZernMC Launcher Parent
+ ZernMC Launcher - Multi-module project
+
+
+ bootstrap
+ launcher
+
21
@@ -15,58 +23,84 @@
UTF-8
ZernMC
2026
- ZernMC Launcher - just a minimalistic launcher by SashegDev
- me.sashegdev.zernmc.launcher.Main
+ ZernMC Launcher - Multi-module project
-
-
- org.apache.httpcomponents
- httpclient
- 4.5.14
-
-
- com.fasterxml.jackson.core
- jackson-databind
- 2.15.2
-
-
- com.google.code.gson
- gson
- 2.10.1
-
-
- org.json
- json
- 20231013
-
-
- org.fusesource.jansi
- jansi
- 2.4.1
-
-
- org.jline
- jline
- 3.24.1
-
-
- me.tongfei
- progressbar
- 0.9.5
-
-
- commons-io
- commons-io
- 2.15.1
-
-
- org.junit.jupiter
- junit-jupiter
- 5.10.1
- test
-
-
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.14
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.15.2
+
+
+ com.google.code.gson
+ gson
+ 2.10.1
+
+
+ org.json
+ json
+ 20231013
+
+
+ org.fusesource.jansi
+ jansi
+ 2.4.1
+
+
+ org.jline
+ jline
+ 3.24.1
+
+
+ me.tongfei
+ progressbar
+ 0.9.5
+
+
+ commons-io
+ commons-io
+ 2.15.1
+
+
+
+ org.openjfx
+ javafx-controls
+ 21
+ win
+
+
+ org.openjfx
+ javafx-web
+ 21
+ win
+
+
+ org.openjfx
+ javafx-graphics
+ 21
+ win
+
+
+ org.openjfx
+ javafx-base
+ 21
+ win
+
+
+ org.openjfx
+ javafx-media
+ 21
+ win
+
+
+
@@ -88,7 +122,7 @@
shade
- ../server/builds/ZernMCLauncher.jar
+ ../../server/builds/ZernMCLauncher.jar
${mainClass}
@@ -103,73 +137,7 @@
-
-
-
-
-
- com.akathist.maven.plugins.launch4j
- launch4j-maven-plugin
- 2.5.0
-
-
- l4j
- package
-
- launch4j
-
-
- ../server/builds/ZernMCLauncher-${project.version}.exe
- ../server/builds/ZernMCLauncher.jar
- console
- false
-
- jre21
- 21
-
-
- ${project.version}.0
- ${project.version}
- ZernMC Launcher — just a Minecraft launcher
- ${project.version}.0
- ${project.version}
- ZernMC Launcher
- ZernMC(SashegDev)
- ZernMCLauncher
- ZernMCLauncher-${project.version}.exe
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-antrun-plugin
- 3.1.0
-
-
- package
- run
-
-
- ${project.version}
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/ApiResponse.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/ApiResponse.java
deleted file mode 100644
index c2337ae..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/ApiResponse.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package me.sashegdev.zernmc.launcher.api;
-
-public class ApiResponse {
- private boolean success;
- private T data;
- private String error;
-
- public ApiResponse(boolean success, T data, String error) {
- this.success = success;
- this.data = data;
- this.error = error;
- }
-
- public static ApiResponse success(T data) {
- return new ApiResponse<>(true, data, null);
- }
-
- public static ApiResponse error(String error) {
- return new ApiResponse<>(false, null, error);
- }
-
- public boolean isSuccess() {
- return success;
- }
-
- public T getData() {
- return data;
- }
-
- public String getError() {
- return error;
- }
-}
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/LauncherAPI.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/LauncherAPI.java
deleted file mode 100644
index a0c4795..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/LauncherAPI.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package me.sashegdev.zernmc.launcher.api;
-
-import me.sashegdev.zernmc.launcher.api.auth.AuthService;
-import me.sashegdev.zernmc.launcher.api.instance.InstanceService;
-import me.sashegdev.zernmc.launcher.api.launch.LaunchService;
-
-import java.util.List;
-
-/**
- * Центральный фасад для внутреннего API лаунчера.
- * Используется как единая точка входа для UI и других компонентов.
- */
-public class LauncherAPI {
-
- private final AuthService authService;
- private final InstanceService instanceService;
- private final LaunchService launchService;
-
- public LauncherAPI() {
- this.authService = new AuthService();
- this.instanceService = new InstanceService();
- this.launchService = new LaunchService();
- }
-
- public AuthService auth() {
- return authService;
- }
-
- public InstanceService instances() {
- return instanceService;
- }
-
- public LaunchService launch() {
- return launchService;
- }
-
- // ====================== Удобные методы ======================
-
- public boolean isLoggedIn() {
- return authService.isLoggedIn();
- }
-
- public String getCurrentUsername() {
- return authService.getCurrentUsername();
- }
-
- public ApiResponse checkSession() {
- return authService.checkSession();
- }
-
- public ApiResponse login(String username, String password) {
- return authService.login(username, password);
- }
-
- public ApiResponse logout() {
- return authService.logout();
- }
-
- public ApiResponse> getAllInstances() {
- return instanceService.getAllInstances();
- }
-
- public ApiResponse getLaunchInfo(String instanceName) {
- return launchService.getLaunchInfo(instanceName);
- }
-
- public ApiResponse prepareLaunch(String instanceName) {
- return launchService.prepareLaunch(instanceName);
- }
-
- public ApiResponse launch(String instanceName) {
- return launchService.launch(instanceName);
- }
-}
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/auth/AuthService.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/auth/AuthService.java
deleted file mode 100644
index 48097c0..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/auth/AuthService.java
+++ /dev/null
@@ -1,134 +0,0 @@
-package me.sashegdev.zernmc.launcher.api.auth;
-
-import me.sashegdev.zernmc.launcher.api.ApiResponse;
-import me.sashegdev.zernmc.launcher.auth.AuthManager;
-import me.sashegdev.zernmc.launcher.utils.ZHttpClient;
-
-import java.io.IOException;
-
-public class AuthService {
-
- public ApiResponse login(String username, String password) {
- try {
- 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 : "Неверный логин или пароль");
- } catch (Exception e) {
- return ApiResponse.error("Ошибка авторизации: " + e.getMessage());
- }
- }
-
- public ApiResponse logout() {
- try {
- AuthManager.logout();
- return ApiResponse.success(true);
- } catch (Exception e) {
- return ApiResponse.error("Ошибка при выходе: " + e.getMessage());
- }
- }
-
- public ApiResponse checkSession() {
- try {
- boolean restored = AuthManager.loadSavedSession();
- if (restored) {
- SessionInfo info = new SessionInfo(
- AuthManager.getUsername(),
- AuthManager.getAccessToken(),
- AuthManager.hasActivePass()
- );
- return ApiResponse.success(info);
- }
- return ApiResponse.error("Сессия не найдена");
- } catch (Exception e) {
- return ApiResponse.error("Ошибка проверки сессии: " + e.getMessage());
- }
- }
-
- public ApiResponse activatePass(String passCode) {
- try {
- String response = post("/auth/pass/activate",
- "{\"code\":\"" + passCode + "\"}");
- return ApiResponse.success(true);
- } catch (Exception e) {
- return ApiResponse.error("Ошибка активации проходки: " + e.getMessage());
- }
- }
-
- private String post(String endpoint, String jsonBody) throws Exception {
- String fullUrl = ZHttpClient.getBaseUrl() + endpoint;
- java.net.URL url = new java.net.URL(fullUrl);
- java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection();
-
- conn.setRequestMethod("POST");
- conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
- conn.setRequestProperty("Accept", "application/json");
- conn.setRequestProperty("User-Agent", "ZernMC-Launcher/1.0");
-
- if (AuthManager.getAccessToken() != null && !AuthManager.getAccessToken().equals("0")) {
- conn.setRequestProperty("Authorization", "Bearer " + AuthManager.getAccessToken());
- }
-
- conn.setDoOutput(true);
-
- try (var os = conn.getOutputStream()) {
- byte[] input = jsonBody.getBytes(java.nio.charset.StandardCharsets.UTF_8);
- os.write(input);
- }
-
- int statusCode = conn.getResponseCode();
- var is = (statusCode >= 200 && statusCode < 300) ? conn.getInputStream() : conn.getErrorStream();
-
- String responseBody;
- try (var scanner = new java.util.Scanner(is, java.nio.charset.StandardCharsets.UTF_8.name())) {
- responseBody = scanner.useDelimiter("\\A").hasNext() ? scanner.next() : "";
- }
-
- conn.disconnect();
-
- if (statusCode != 200) {
- throw new IOException("HTTP " + statusCode + ": " + responseBody);
- }
-
- return responseBody;
- }
-
- public boolean isLoggedIn() {
- return AuthManager.isLoggedIn();
- }
-
- public String getCurrentUsername() {
- return AuthManager.getUsername();
- }
-
- public static class LoginResult {
- private String username;
- private String token;
-
- public LoginResult(String username, String token) {
- this.username = username;
- this.token = token;
- }
-
- public String getUsername() { return username; }
- public String getToken() { return token; }
- }
-
- public static class SessionInfo {
- private String username;
- private String token;
- private boolean passActive;
-
- public SessionInfo(String username, String token, boolean passActive) {
- this.username = username;
- this.token = token;
- this.passActive = passActive;
- }
-
- public String getUsername() { return username; }
- public String getToken() { return token; }
- public boolean isPassActive() { return passActive; }
- }
-}
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/instance/InstanceService.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/instance/InstanceService.java
deleted file mode 100644
index 7ce0647..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/instance/InstanceService.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package me.sashegdev.zernmc.launcher.api.instance;
-
-import me.sashegdev.zernmc.launcher.api.ApiResponse;
-import me.sashegdev.zernmc.launcher.minecraft.Instance;
-import me.sashegdev.zernmc.launcher.minecraft.InstanceManager;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class InstanceService {
-
- public ApiResponse> getAllInstances() {
- try {
- List instances = InstanceManager.getAllInstances();
- List infoList = instances.stream()
- .map(this::toInstanceInfo)
- .collect(Collectors.toList());
- return ApiResponse.success(infoList);
- } catch (IOException e) {
- return ApiResponse.error("Ошибка получения списка сборок: " + e.getMessage());
- }
- }
-
- public ApiResponse getInstance(String name) {
- try {
- Instance instance = InstanceManager.getInstance(name);
- if (instance == null) {
- return ApiResponse.error("Сборка не найдена: " + name);
- }
- return ApiResponse.success(toInstanceInfo(instance));
- } catch (Exception e) {
- return ApiResponse.error("Ошибка получения сборки: " + e.getMessage());
- }
- }
-
- public ApiResponse createInstance(String name) {
- try {
- boolean created = InstanceManager.createInstanceFolder(name);
- if (!created) {
- return ApiResponse.error("Сборка с таким именем уже существует: " + name);
- }
- Instance instance = InstanceManager.getInstance(name);
- return ApiResponse.success(toInstanceInfo(instance));
- } catch (IOException e) {
- return ApiResponse.error("Ошибка создания сборки: " + e.getMessage());
- }
- }
-
- public ApiResponse deleteInstance(String name) {
- try {
- boolean deleted = InstanceManager.deleteInstance(name);
- if (!deleted) {
- return ApiResponse.error("Не удалось удалить сборку: " + name);
- }
- return ApiResponse.success(true);
- } catch (Exception e) {
- return ApiResponse.error("Ошибка удаления сборки: " + e.getMessage());
- }
- }
-
- public ApiResponse isInstanceExists(String name) {
- try {
- Instance instance = InstanceManager.getInstance(name);
- return ApiResponse.success(instance != null);
- } catch (Exception e) {
- return ApiResponse.error("Ошибка проверки сборки: " + e.getMessage());
- }
- }
-
- private InstanceInfo toInstanceInfo(Instance instance) {
- return new InstanceInfo(
- instance.getName(),
- instance.getPath().toString(),
- instance.getMinecraftVersion(),
- instance.getLoaderType()
- );
- }
-
- public static class InstanceInfo {
- private String name;
- private String path;
- private String version;
- private String loaderType;
-
- public InstanceInfo(String name, String path, String version, String loaderType) {
- this.name = name;
- this.path = path;
- this.version = version;
- this.loaderType = loaderType;
- }
-
- public String getName() { return name; }
- public String getPath() { return path; }
- public String getVersion() { return version; }
- public String getLoaderType() { return loaderType; }
- }
-}
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/launch/LaunchService.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/launch/LaunchService.java
deleted file mode 100644
index c203138..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/api/launch/LaunchService.java
+++ /dev/null
@@ -1,157 +0,0 @@
-package me.sashegdev.zernmc.launcher.api.launch;
-
-import me.sashegdev.zernmc.launcher.api.ApiResponse;
-import me.sashegdev.zernmc.launcher.minecraft.Instance;
-import me.sashegdev.zernmc.launcher.minecraft.InstanceManager;
-import me.sashegdev.zernmc.launcher.minecraft.launch.LaunchCommandBuilder;
-import me.sashegdev.zernmc.launcher.minecraft.model.LaunchOptions;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.List;
-
-public class LaunchService {
-
- public ApiResponse prepareLaunch(String instanceName) {
- try {
- Instance instance = InstanceManager.getInstance(instanceName);
- if (instance == null) {
- return ApiResponse.error("Сборка не найдена: " + instanceName);
- }
-
- LaunchCommandBuilder builder = new LaunchCommandBuilder(instance);
- LaunchOptions options = new LaunchOptions();
-
- List command = builder.build(options);
-
- LaunchInfo info = new LaunchInfo(
- instanceName,
- command,
- instance.getPath().toString()
- );
- return ApiResponse.success(info);
- } catch (Exception e) {
- return ApiResponse.error("Ошибка подготовки запуска: " + e.getMessage());
- }
- }
-
- public ApiResponse launch(String instanceName) {
- try {
- Instance instance = InstanceManager.getInstance(instanceName);
- if (instance == null) {
- return ApiResponse.error("Сборка не найдена: " + instanceName);
- }
-
- LaunchCommandBuilder builder = new LaunchCommandBuilder(instance);
- LaunchOptions options = new LaunchOptions();
-
- List command = builder.build(options);
-
- ProcessBuilder processBuilder = new ProcessBuilder(command);
- processBuilder.directory(instance.getPath().toFile());
- processBuilder.inheritIO();
-
- Process process = processBuilder.start();
-
- ProcessInfo info = new ProcessInfo(
- instanceName,
- process.pid(),
- "RUNNING"
- );
- return ApiResponse.success(info);
- } catch (Exception e) {
- return ApiResponse.error("Ошибка запуска: " + e.getMessage());
- }
- }
-
- public ApiResponse isReady(String instanceName) {
- try {
- Instance instance = InstanceManager.getInstance(instanceName);
- if (instance == null) {
- return ApiResponse.error("Сборка не найдена: " + instanceName);
- }
-
- Path versionJson = instance.getPath().resolve("version.json");
- boolean hasVersionJson = versionJson.toFile().exists();
-
- return ApiResponse.success(hasVersionJson);
- } catch (Exception e) {
- return ApiResponse.error("Ошибка проверки готовности: " + e.getMessage());
- }
- }
-
- public ApiResponse getLaunchInfo(String instanceName) {
- try {
- Instance instance = InstanceManager.getInstance(instanceName);
- if (instance == null) {
- return ApiResponse.error("Сборка не найдена: " + instanceName);
- }
-
- InstanceInfo info = new InstanceInfo(
- instance.getName(),
- instance.getMinecraftVersion(),
- instance.getLoaderType(),
- instance.getLoaderVersion(),
- instance.getAssetIndex()
- );
- return ApiResponse.success(info);
- } catch (Exception e) {
- return ApiResponse.error("Ошибка получения информации: " + e.getMessage());
- }
- }
-
- public static class LaunchInfo {
- private String instanceName;
- private List command;
- private String workingDirectory;
-
- public LaunchInfo(String instanceName, List command, String workingDirectory) {
- this.instanceName = instanceName;
- this.command = command;
- this.workingDirectory = workingDirectory;
- }
-
- public String getInstanceName() { return instanceName; }
- public List getCommand() { return command; }
- public String getWorkingDirectory() { return workingDirectory; }
- }
-
- public static class ProcessInfo {
- private String instanceName;
- private long pid;
- private String status;
-
- public ProcessInfo(String instanceName, long pid, String status) {
- this.instanceName = instanceName;
- this.pid = pid;
- this.status = status;
- }
-
- public String getInstanceName() { return instanceName; }
- public long getPid() { return pid; }
- public String getStatus() { return status; }
- }
-
- public static class InstanceInfo {
- private String name;
- private String minecraftVersion;
- private String loaderType;
- private String loaderVersion;
- private String assetIndex;
-
- public InstanceInfo(String name, String minecraftVersion, String loaderType,
- String loaderVersion, String assetIndex) {
- this.name = name;
- this.minecraftVersion = minecraftVersion;
- this.loaderType = loaderType;
- this.loaderVersion = loaderVersion;
- this.assetIndex = assetIndex;
- }
-
- public String getName() { return name; }
- public String getMinecraftVersion() { return minecraftVersion; }
- public String getLoaderType() { return loaderType; }
- public String getLoaderVersion() { return loaderVersion; }
- public String getAssetIndex() { return assetIndex; }
- }
-}
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/auth/AuthManager.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/auth/AuthManager.java
deleted file mode 100644
index d169b23..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/auth/AuthManager.java
+++ /dev/null
@@ -1,354 +0,0 @@
-package me.sashegdev.zernmc.launcher.auth;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.google.gson.annotations.SerializedName;
-import me.sashegdev.zernmc.launcher.utils.Config;
-import me.sashegdev.zernmc.launcher.utils.ZAnsi;
-import me.sashegdev.zernmc.launcher.utils.ZHttpClient;
-
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-
-public class AuthManager {
-
- private static final Path AUTH_FILE = Config.getConfigDir().resolve("auth.json");
- private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
-
- private static volatile AuthSession session = null;
- private static volatile UserInfo userInfo = null;
-
- // === Роли ===
- public static final int ROLE_USER = 0;
- public static final int ROLE_PASS_HOLDER = 1;
- public static final int ROLE_MODERATOR = 2;
- public static final int ROLE_ELDER = 3;
- public static final int ROLE_CREATOR = 4;
-
- // === Права доступа ===
- public static final String PERM_VIEW_PACKS = "view_packs";
- public static final String PERM_DOWNLOAD_PACK = "download_pack";
-
- public static boolean loadSavedSession() {
- if (!Files.exists(AUTH_FILE)) return false;
- try {
- String json = Files.readString(AUTH_FILE);
- AuthSession loaded = GSON.fromJson(json, AuthSession.class);
- if (loaded == null || loaded.accessToken == null) return false;
-
- session = loaded;
- userInfo = fetchUserInfo();
-
- if (isAccessTokenExpired()) {
- return tryRefresh();
- }
- return true;
- } catch (Exception e) {
- return false;
- }
- }
-
- // ====================== АВТОРИЗАЦИЯ ======================
- public static AuthResult login(String username, String password) {
- return authRequest("/auth/login", username, password);
- }
-
- public static AuthResult register(String username, String password) {
- return authRequest("/auth/register", username, password);
- }
-
- private static AuthResult authRequest(String endpoint, String username, String password) {
- try {
- String body = GSON.toJson(new LoginRequest(username, password));
- SimpleHttpResponse resp = post(endpoint, body);
-
- if (resp.statusCode() == 200) {
- session = GSON.fromJson(resp.body(), AuthSession.class);
- session.expiresAt = System.currentTimeMillis() / 1000L + session.expiresIn;
- saveSession();
- userInfo = fetchUserInfo();
- return AuthResult.ok();
- } else if (resp.statusCode() == 422) {
- return AuthResult.fail("Ошибка валидации: " + extractError(resp.body()));
- } else {
- return AuthResult.fail(extractError(resp.body()));
- }
- } catch (Exception e) {
- e.printStackTrace();
- return AuthResult.fail("Ошибка соединения: " + e.getMessage());
- }
- }
-
- public static void logout() {
- if (session != null && session.refreshToken != null) {
- try {
- post("/auth/logout", "{\"refresh_token\":\"" + session.refreshToken + "\"}");
- } catch (Exception ignored) {}
- }
- session = null;
- userInfo = null;
- try { Files.deleteIfExists(AUTH_FILE); } catch (Exception ignored) {}
- }
-
- public static boolean isLoggedIn() {
- return session != null && session.accessToken != null;
- }
-
- public static String getUsername() {
- return session != null ? session.username : "Player";
- }
-
- public static String getUuid() {
- return session != null ? session.uuid : "00000000-0000-0000-0000-000000000000";
- }
-
- public static String getAccessToken() {
- if (session == null) return "0";
- if (isAccessTokenExpired()) {
- tryRefresh();
- }
- return session != null && session.accessToken != null ? session.accessToken : "0";
- }
-
- private static boolean isAccessTokenExpired() {
- if (session == null) return true;
- return System.currentTimeMillis() / 1000L >= session.expiresAt - 300;
- }
-
- private static boolean tryRefresh() {
- if (session == null || session.refreshToken == null) return false;
- try {
- String body = "{\"refresh_token\":\"" + session.refreshToken + "\"}";
- SimpleHttpResponse resp = post("/auth/refresh", body);
-
- if (resp.statusCode() == 200) {
- AuthSession newSession = GSON.fromJson(resp.body(), AuthSession.class);
- newSession.expiresAt = System.currentTimeMillis() / 1000L + newSession.expiresIn;
- session = newSession;
- userInfo = fetchUserInfo();
- saveSession();
- return true;
- }
- } catch (Exception ignored) {}
- session = null;
- userInfo = null;
- try { Files.deleteIfExists(AUTH_FILE); } catch (Exception ignored) {}
- return false;
- }
-
- private static void saveSession() {
- try {
- Files.createDirectories(AUTH_FILE.getParent());
- Files.writeString(AUTH_FILE, GSON.toJson(session));
- } catch (IOException e) {
- System.err.println(ZAnsi.yellow("Не удалось сохранить сессию: " + e.getMessage()));
- }
- }
-
- // ==================== ПОЛУЧЕНИЕ ИНФОРМАЦИИ О ПОЛЬЗОВАТЕЛЕ ====================
- private static UserInfo fetchUserInfo() {
- if (!isLoggedIn() || session.accessToken == null) return null;
-
- try {
- // Используем существующий метод ZHttpClient.get() + вручную добавляем токен
- java.net.HttpURLConnection conn = null;
- try {
- URL url = new URL(ZHttpClient.getBaseUrl() + "/admin/me");
- conn = (java.net.HttpURLConnection) url.openConnection();
- conn.setRequestMethod("GET");
- conn.setRequestProperty("Accept", "application/json");
- conn.setRequestProperty("Authorization", "Bearer " + session.accessToken);
- conn.setConnectTimeout(10000);
- conn.setReadTimeout(10000);
-
- int responseCode = conn.getResponseCode();
- if (responseCode != 200) return null;
-
- StringBuilder response = new StringBuilder();
- try (var reader = new java.io.BufferedReader(
- new java.io.InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
- String line;
- while ((line = reader.readLine()) != null) {
- response.append(line);
- }
- }
- return GSON.fromJson(response.toString(), UserInfo.class);
- } finally {
- if (conn != null) conn.disconnect();
- }
- } catch (Exception e) {
- System.err.println("Не удалось получить UserInfo: " + e.getMessage());
- return null;
- }
- }
-
- // ==================== ПРОВЕРКИ ПРАВ ====================
- public static boolean hasPass() {
- if (userInfo != null) return userInfo.has_pass;
- return getRole() >= ROLE_PASS_HOLDER;
- }
-
- public static boolean canViewPacks() {
- if (userInfo != null && userInfo.permissions != null) {
- return userInfo.permissions.contains(PERM_VIEW_PACKS);
- }
- return hasPass(); // fallback для старых аккаунтов
- }
-
- public static boolean canDownloadPacks() {
- if (userInfo != null && userInfo.permissions != null) {
- return userInfo.permissions.contains(PERM_DOWNLOAD_PACK);
- }
- return hasPass(); // fallback
- }
-
- public static int getRole() {
- return session != null ? session.role : ROLE_USER;
- }
-
- // ====================== POST ======================
- private static SimpleHttpResponse post(String endpoint, String jsonBody) throws Exception {
- String fullUrl = ZHttpClient.getBaseUrl() + endpoint;
- HttpURLConnection conn = null;
-
- try {
- URL url = new URL(fullUrl);
- conn = (HttpURLConnection) url.openConnection();
-
- conn.setRequestMethod("POST");
- conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
- conn.setRequestProperty("Accept", "application/json");
- conn.setRequestProperty("User-Agent", "ZernMC-Launcher/1.0");
- conn.setRequestProperty("Connection", "close");
-
- if (session != null && session.accessToken != null) {
- conn.setRequestProperty("Authorization", "Bearer " + session.accessToken);
- }
-
- conn.setDoOutput(true);
- conn.setConnectTimeout(15000);
- conn.setReadTimeout(15000);
-
- byte[] input = jsonBody.getBytes(StandardCharsets.UTF_8);
- conn.setFixedLengthStreamingMode(input.length);
-
- try (var os = conn.getOutputStream()) {
- os.write(input);
- os.flush();
- }
-
- int statusCode = conn.getResponseCode();
- var is = (statusCode >= 200 && statusCode < 300) ? conn.getInputStream() : conn.getErrorStream();
-
- String responseBody;
- try (var scanner = new java.util.Scanner(is, StandardCharsets.UTF_8.name())) {
- responseBody = scanner.useDelimiter("\\A").hasNext() ? scanner.next() : "";
- }
-
- return new SimpleHttpResponse(statusCode, responseBody);
-
- } finally {
- if (conn != null) conn.disconnect();
- }
- }
-
- private static String extractError(String body) {
- try {
- JsonObject json = JsonParser.parseString(body).getAsJsonObject();
- if (json.has("detail")) {
- if (json.get("detail").isJsonArray()) {
- return json.getAsJsonArray("detail").get(0).getAsJsonObject().get("msg").getAsString();
- }
- return json.get("detail").getAsString();
- }
- } catch (Exception ignored) {}
- return body.length() > 200 ? body.substring(0, 200) + "..." : body;
- }
-
- public static boolean hasActivePass() {
- if (!isLoggedIn()) return false;
- try {
- String response = ZHttpClient.get("/auth/pass/my");
- JsonObject json = JsonParser.parseString(response).getAsJsonObject();
- return json.has("has_active") && json.get("has_active").getAsBoolean();
- } catch (Exception e) {
- System.err.println(ZAnsi.red("Не удалось проверить проходки: ") + e.getMessage());
- return false;
- }
- }
-
- public static String getPassStatus() {
- if (!isLoggedIn()) return "Не авторизован";
- try {
- String response = ZHttpClient.get("/auth/pass/my");
- JsonObject json = JsonParser.parseString(response).getAsJsonObject();
- boolean hasActive = json.has("has_active") && json.get("has_active").getAsBoolean();
- return hasActive ? "Есть активная проходка" : "Проходка отсутствует";
- } catch (Exception e) {
- return "Ошибка проверки";
- }
- }
-
- // ====================== ВНУТРЕННИЕ КЛАССЫ ======================
- public static class AuthSession {
- @SerializedName("access_token") public String accessToken;
- @SerializedName("refresh_token") public String refreshToken;
- @SerializedName("expires_in") public int expiresIn;
- public transient long expiresAt;
- public String username;
- public String uuid;
- public int role;
- }
-
- public static class UserInfo {
- public int id;
- public String username;
- public String uuid;
- public int role;
- public String role_name;
- public boolean has_pass;
- public List permissions;
-
- public boolean hasPermission(String perm) {
- return permissions != null && permissions.contains(perm);
- }
- }
-
- private static class LoginRequest {
- final String username;
- final String password;
- LoginRequest(String u, String p) {
- this.username = u;
- this.password = p;
- }
- }
-
- public static class AuthResult {
- public final boolean success;
- public final String error;
- private AuthResult(boolean s, String e) { success = s; error = e; }
- public static AuthResult ok() { return new AuthResult(true, null); }
- public static AuthResult fail(String msg) { return new AuthResult(false, msg); }
- }
-}
-
-// ====================== ВСПОМОГАТЕЛЬНЫЙ КЛАСС ======================
-class SimpleHttpResponse {
- final int statusCode;
- final String body;
-
- SimpleHttpResponse(int statusCode, String body) {
- this.statusCode = statusCode;
- this.body = body;
- }
-
- int statusCode() { return statusCode; }
- String body() { return body; }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/LaunchMenu.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/LaunchMenu.java
deleted file mode 100644
index 9d75539..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/LaunchMenu.java
+++ /dev/null
@@ -1,770 +0,0 @@
-package me.sashegdev.zernmc.launcher.menu;
-
-import me.sashegdev.zernmc.launcher.auth.AuthManager;
-import me.sashegdev.zernmc.launcher.minecraft.Instance;
-import me.sashegdev.zernmc.launcher.minecraft.InstanceManager;
-import me.sashegdev.zernmc.launcher.minecraft.MinecraftLib;
-import me.sashegdev.zernmc.launcher.minecraft.PackDownloader;
-import me.sashegdev.zernmc.launcher.minecraft.ServerPack;
-import me.sashegdev.zernmc.launcher.minecraft.installer.VersionInstaller;
-import me.sashegdev.zernmc.launcher.minecraft.model.LaunchOptions;
-import me.sashegdev.zernmc.launcher.minecraft.model.MinecraftVersion;
-import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
-import me.sashegdev.zernmc.launcher.utils.Config;
-import me.sashegdev.zernmc.launcher.utils.ConsoleUtils;
-import me.sashegdev.zernmc.launcher.utils.Input;
-import me.sashegdev.zernmc.launcher.utils.ZAnsi;
-import me.sashegdev.zernmc.launcher.utils.ZHttpClient;
-
-import java.awt.*;
-import java.io.IOException;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class LaunchMenu {
-
- public void show() throws Exception {
- if (Config.isZernMCBuild()) {
- showZernMCOnly();
- } else {
- showGlobal();
- }
- }
-
- // ====================== ZERNMC BUILD ======================
- private void showZernMCOnly() throws Exception {
- while (true) {
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.header("=== ZernMC Private Launcher ==="));
- System.out.println(ZAnsi.cyan("Доступны только серверные сборки"));
-
- if (!awaitActivePass()) {
- return;
- }
-
- PackDownloader tempDownloader = new PackDownloader(null);
- List availablePacks = tempDownloader.getAvailablePacks();
-
- if (availablePacks.isEmpty()) {
- System.out.println(ZAnsi.yellow("На данный момент нет доступных сборок на сервере."));
- ConsoleUtils.pause();
- return;
- }
-
- List options = availablePacks.stream()
- .map(p -> String.format("%s [%s + %s v%d] — %d файлов",
- p.getName(),
- p.getMinecraftVersion(),
- p.getLoaderType(),
- p.getVersion(),
- p.getFilesCount()))
- .collect(Collectors.toList());
-
- options.add("Назад в главное меню");
-
- ArrowMenu menu = new ArrowMenu("Выберите сборку", options);
- int choice = menu.show();
-
- if (choice == -1 || choice == options.size() - 1) return;
-
- ServerPack selected = availablePacks.get(choice);
- installAndRunServerPack(selected);
- }
- }
-
- private boolean awaitActivePass() throws Exception {
- if (AuthManager.hasActivePass()) {
- System.out.println(ZAnsi.brightGreen("✓ Активная проходка подтверждена"));
- return true;
- }
-
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.brightRed("У вас нет активной проходки!"));
- System.out.println(ZAnsi.white("Для доступа к сборкам ZernMC требуется активная проходка."));
- System.out.println();
-
- openActivationWebsite();
-
- System.out.println(ZAnsi.cyan("Ожидаем активацию проходки... (проверка каждые 10 секунд)"));
- System.out.println(ZAnsi.white("Нажмите Enter для отмены"));
-
- for (int i = 0; i < 60; i++) {
- try {
- if (System.in.available() > 0) {
- Input.readLine();
- System.out.println(ZAnsi.yellow("\nОжидание отменено."));
- return false;
- }
- } catch (Exception ignored) {}
-
- Thread.sleep(10000);
-
- if (AuthManager.hasActivePass()) {
- System.out.println(ZAnsi.brightGreen("\n✓ Проходка успешно активирована!"));
- return true;
- }
-
- System.out.print(ZAnsi.cyan("."));
- if ((i + 1) % 6 == 0) System.out.println();
- }
-
- System.out.println(ZAnsi.brightRed("\n\nВремя ожидания истекло."));
- return false;
- }
-
- private void openActivationWebsite() {
- //String url = "https://launcher.ru.zernmc.ru/activate-pass";
- String url = ZHttpClient.getBaseUrl() + "/activate-pass";
-
- try {
- if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
- Desktop.getDesktop().browse(new URI(url));
- System.out.println(ZAnsi.cyan("Браузер открыт: " + url));
- } else {
- System.out.println(ZAnsi.yellow("Не удалось открыть браузер автоматически."));
- System.out.println(ZAnsi.white("Откройте вручную: " + url));
- }
- } catch (Exception e) {
- System.out.println(ZAnsi.brightRed("Ошибка открытия браузера: " + e.getMessage()));
- System.out.println(ZAnsi.white("Ссылка: " + url));
- }
- }
-
- private void installAndRunServerPack(ServerPack selected) throws Exception {
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.header("Установка сборки: " + selected.getName()));
-
- System.out.println(ZAnsi.white(" Minecraft: ") + selected.getMinecraftVersion());
- System.out.println(ZAnsi.white(" Лоадер: ") + selected.getLoaderType() +
- (selected.getLoaderVersion() != null ? " " + selected.getLoaderVersion() : ""));
- System.out.println(ZAnsi.white(" Версия: v") + selected.getVersion());
- System.out.println(ZAnsi.white(" Файлов: ") + selected.getFilesCount());
-
- String localName = askPackName();
- if (localName == null) return;
-
- if (InstanceManager.getInstance(localName) != null) {
- System.out.println(ZAnsi.brightRed("Сборка с таким именем уже существует!"));
- ConsoleUtils.pause();
- return;
- }
-
- InstanceManager.createInstanceFolder(localName);
- Instance newInstance = InstanceManager.getInstance(localName);
-
- PackDownloader packDownloader = new PackDownloader(newInstance);
- boolean success = packDownloader.installOrUpdatePack(selected.getName(), selected);
-
- if (!success) {
- System.out.println(ZAnsi.brightRed("\n[FAIL] Не удалось установить сборку."));
- ConsoleUtils.pause();
- return;
- }
-
- System.out.println(ZAnsi.brightGreen("\n[OK] Сборка '" + localName + "' успешно установлена!"));
- ConsoleUtils.pause();
-
- launchExistingInstance(newInstance);
- }
-
- // ====================== GLOBAL BUILD ======================
- private void showGlobal() throws Exception {
- while (true) {
- ConsoleUtils.clearScreen();
- List instances = InstanceManager.getAllInstances();
-
- List options = instances.stream()
- .map(Instance::toString)
- .collect(Collectors.toList());
-
- options.add("Установить новую сборку");
- options.add("Назад в главное меню");
-
- ArrowMenu menu = new ArrowMenu("Управление сборками", options);
- int choice = menu.show();
-
- if (choice == -1 || choice == options.size() - 1) break;
-
- if (choice == instances.size()) {
- installNewPackGlobal();
- continue;
- }
-
- Instance selected = instances.get(choice);
- manageInstance(selected);
- }
- }
-
- private void installNewPackGlobal() throws Exception {
- ConsoleUtils.clearScreen();
-
- List options = List.of(
- "Установить сборку с сервера ZernMC",
- "Установить Vanilla Minecraft",
- "Создать сборку вручную (Fabric/Forge)",
- "Назад"
- );
-
- ArrowMenu menu = new ArrowMenu("Установка новой сборки", options);
- int choice = menu.show();
-
- if (choice == -1 || choice == 3) return;
-
- switch (choice) {
- case 0 -> installServerPackGlobal();
- case 1 -> createVanillaInstance();
- case 2 -> createCustomInstance();
- }
- }
-
- private void installServerPackGlobal() throws Exception {
- if (!awaitActivePass()) return;
-
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.cyan("Получение списка доступных сборок..."));
-
- PackDownloader tempDownloader = new PackDownloader(null);
- List availablePacks = tempDownloader.getAvailablePacks();
-
- if (availablePacks.isEmpty()) {
- System.out.println(ZAnsi.yellow("Нет доступных сборок на сервере."));
- ConsoleUtils.pause();
- return;
- }
-
- List options = availablePacks.stream()
- .map(p -> String.format("%s [%s + %s v%d] — %d файлов",
- p.getName(),
- p.getMinecraftVersion(),
- p.getLoaderType(),
- p.getVersion(),
- p.getFilesCount()))
- .collect(Collectors.toList());
- options.add("Назад");
-
- ArrowMenu menu = new ArrowMenu("Выберите сборку для установки", options);
- int choice = menu.show();
-
- if (choice == -1 || choice == options.size() - 1) return;
-
- ServerPack selected = availablePacks.get(choice);
-
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.header("Установка сборки: " + selected.getName()));
-
- System.out.print(ZAnsi.white("\nВведите название локальной сборки (Enter = имя пака): "));
- String localName = Input.readLine().trim();
- if (localName.isEmpty()) localName = selected.getName();
-
- if (InstanceManager.getInstance(localName) != null) {
- System.out.println(ZAnsi.brightRed("Сборка с таким именем уже существует!"));
- ConsoleUtils.pause();
- return;
- }
-
- InstanceManager.createInstanceFolder(localName);
- Instance newInstance = InstanceManager.getInstance(localName);
-
- PackDownloader packDownloader = new PackDownloader(newInstance);
- boolean success = packDownloader.installOrUpdatePack(selected.getName(), selected);
-
- if (success) {
- System.out.println(ZAnsi.brightGreen("\n[OK] Сборка '" + localName + "' успешно установлена!"));
- } else {
- System.out.println(ZAnsi.brightRed("\n[FAIL] Не удалось установить сборку."));
- }
-
- ConsoleUtils.pause();
- }
-
- // ====================== manageInstance — полностью восстановлен ======================
- private void manageInstance(Instance instance) throws Exception {
- while (true) {
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.header("Управление сборкой: " + instance.getName()));
- System.out.println(ZAnsi.white("Версия: " + instance.getMinecraftVersion()));
- System.out.println(ZAnsi.white("Лоадер: " + instance.getLoaderType() +
- (instance.getLoaderVersion() != null ? " " + instance.getLoaderVersion() : "")));
-
- if (instance.isServerPack()) {
- System.out.println(ZAnsi.green("Серверная сборка: v" + instance.getServerVersion()));
- }
-
- List options = new ArrayList<>();
- options.add("Запустить сборку");
- if (instance.isServerPack()) {
- options.add("Проверить обновления");
- }
- options.add("Изменить версию лоадера");
- options.add("Удалить сборку");
- options.add("Назад");
-
- ArrowMenu menu = new ArrowMenu("Действия", options);
- int choice = menu.show();
-
- if (choice == -1 || choice == options.size() - 1) return;
-
- switch (choice) {
- case 0 -> launchExistingInstance(instance);
- case 1 -> {
- if (instance.isServerPack()) {
- checkAndUpdateServerPack(instance);
- } else {
- changeLoaderVersion(instance);
- }
- }
- case 2 -> {
- if (instance.isServerPack()) {
- changeLoaderVersion(instance);
- } else {
- deleteInstance(instance);
- }
- }
- case 3 -> deleteInstance(instance);
- }
- }
- }
-
- private void checkAndUpdateServerPack(Instance instance) throws Exception {
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.cyan("Проверка обновлений для " + instance.getName()));
-
- PackDownloader downloader = new PackDownloader(instance);
- boolean hasUpdate = downloader.checkForUpdates(instance.getServerPackName());
-
- if (!hasUpdate) {
- System.out.println(ZAnsi.green("Сборка актуальна (v" + instance.getServerVersion() + ")"));
- ConsoleUtils.pause();
- return;
- }
-
- System.out.println(ZAnsi.brightYellow("Доступно обновление!"));
- if (Input.confirm("Обновить сборку")) {
- boolean success = downloader.updatePack(instance.getServerPackName());
- if (success) {
- System.out.println(ZAnsi.brightGreen("Сборка успешно обновлена!"));
- } else {
- System.out.println(ZAnsi.brightRed("Не удалось обновить сборку."));
- }
- } else {
- System.out.println(ZAnsi.yellow("Обновление отменено."));
- }
- ConsoleUtils.pause();
- }
-
- private void changeLoaderVersion(Instance instance) throws Exception {
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.cyan("Изменение версии лоадера для " + instance.getName()));
-
- String currentLoader = instance.getLoaderType();
- String mcVersion = instance.getMinecraftVersion();
-
- if ("vanilla".equalsIgnoreCase(currentLoader)) {
- System.out.println(ZAnsi.yellow("Это vanilla сборка. Нельзя изменить лоадер."));
- ConsoleUtils.pause();
- return;
- }
-
- String newLoaderVersion;
- if ("fabric".equalsIgnoreCase(currentLoader)) {
- newLoaderVersion = askFabricLoaderVersion();
- } else if ("neoforge".equalsIgnoreCase(currentLoader)) {
- newLoaderVersion = askNeoForgeVersion(mcVersion);
- } else {
- newLoaderVersion = askForgeVersion(mcVersion);
- }
-
- if (newLoaderVersion == null) return;
-
- System.out.println(ZAnsi.cyan("Переустановка лоадера " + currentLoader + " -> " + newLoaderVersion + "..."));
-
- MinecraftLib lib = new MinecraftLib(instance);
- boolean success;
-
- try {
- if ("fabric".equalsIgnoreCase(currentLoader)) {
- success = lib.installFabric(mcVersion, newLoaderVersion);
- } else if ("neoforge".equalsIgnoreCase(currentLoader)) {
- success = lib.installNeoForge(mcVersion, newLoaderVersion);
- } else {
- success = lib.installForge(mcVersion, newLoaderVersion);
- }
-
- if (success) {
- System.out.println(ZAnsi.brightGreen("Версия лоадера успешно изменена!"));
- } else {
- System.out.println(ZAnsi.brightRed("Не удалось изменить версию лоадера."));
- }
- } catch (Exception e) {
- System.out.println(ZAnsi.brightRed("Ошибка при смене лоадера: " + e.getMessage()));
- }
-
- ConsoleUtils.pause();
- }
-
- private void deleteInstance(Instance instance) throws IOException {
- ConsoleUtils.clearScreen();
-
- List confirmOptions = List.of(
- "Да, удалить сборку",
- "Нет, отменить"
- );
-
- ArrowMenu confirmMenu = new ArrowMenu(
- "Вы действительно хотите удалить сборку '" + instance.getName() + "'?",
- confirmOptions
- );
-
- int choice = confirmMenu.show();
-
- if (choice == 0) {
- boolean deleted = InstanceManager.deleteInstance(instance.getName());
- if (deleted) {
- System.out.println(ZAnsi.brightGreen("Сборка '" + instance.getName() + "' успешно удалена."));
- } else {
- System.out.println(ZAnsi.brightRed("Не удалось удалить сборку."));
- }
- } else {
- System.out.println(ZAnsi.yellow("Удаление отменено."));
- }
-
- ConsoleUtils.pause();
- }
-
- private void launchExistingInstance(Instance instance) {
- if (instance.isServerPack() && !AuthManager.hasActivePass()) {
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.brightRed("Для запуска серверной сборки требуется активная проходка!"));
- ConsoleUtils.pause();
- return;
- }
-
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.brightGreen("Запуск сборки: " + instance.getName()));
-
- MinecraftLib lib = new MinecraftLib(instance);
- LaunchOptions options = new LaunchOptions();
-
- options.setUsername(AuthManager.getUsername());
- options.setUuid(AuthManager.getUuid());
- options.setAccessToken(AuthManager.getAccessToken());
-
- try {
- lib.launch(options);
- } catch (Exception e) {
- System.out.println(ZAnsi.brightRed("Ошибка при запуске: " + e.getMessage()));
- e.printStackTrace();
- }
-
- ConsoleUtils.pause();
- }
-
- // ====================== Остальные вспомогательные методы ======================
-
- private String askPackName() {
- System.out.print(ZAnsi.white("\nВведите название новой сборки: "));
- String name = Input.readLine().trim();
- if (name.isEmpty()) {
- System.out.println(ZAnsi.yellow("Отменено."));
- return null;
- }
- return name;
- }
-
- private void createVanillaInstance() throws Exception {
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.cyan("Получение списка версий Minecraft..."));
-
- VersionInstaller versionInstaller = new VersionInstaller(null);
- List allVersions = versionInstaller.getAvailableVersions();
-
- List versionOptions = allVersions.stream()
- .map(v -> v.getId() + " (" + v.getType() + ")")
- .collect(Collectors.toList());
- versionOptions.add("Назад");
-
- ArrowMenu versionMenu = new ArrowMenu("Выбор версии Minecraft", versionOptions);
- int versionChoice = versionMenu.show();
-
- if (versionChoice == -1 || versionChoice == versionOptions.size() - 1) return;
-
- MinecraftVersion selectedMc = allVersions.get(versionChoice);
- String mcVersion = selectedMc.getId();
-
- String packName = askPackName();
- if (packName == null) return;
-
- if (InstanceManager.getInstance(packName) != null) {
- System.out.println(ZAnsi.brightRed("Сборка с таким именем уже существует!"));
- ConsoleUtils.pause();
- return;
- }
-
- InstanceManager.createInstanceFolder(packName);
- Instance newInstance = InstanceManager.getInstance(packName);
-
- MinecraftLib lib = new MinecraftLib(newInstance);
- boolean success = lib.installMinecraft(mcVersion);
-
- if (success) {
- System.out.println(ZAnsi.brightGreen("\n[OK] Vanilla сборка '" + packName + "' успешно создана!"));
- } else {
- System.out.println(ZAnsi.brightRed("\n[FAIL] Не удалось создать сборку."));
- }
-
- ConsoleUtils.pause();
- }
-
- private void createCustomInstance() throws Exception {
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.cyan("Получение списка версий Minecraft..."));
-
- VersionInstaller versionInstaller = new VersionInstaller(null);
- List allVersions = versionInstaller.getAvailableVersions();
-
- List versionOptions = allVersions.stream()
- .map(v -> v.getId() + " (" + v.getType() + ")")
- .collect(Collectors.toList());
- versionOptions.add("Назад");
-
- ArrowMenu versionMenu = new ArrowMenu("Выбор версии Minecraft", versionOptions);
- int versionChoice = versionMenu.show();
-
- if (versionChoice == -1 || versionChoice == versionOptions.size() - 1) return;
-
- MinecraftVersion selectedMc = allVersions.get(versionChoice);
- String mcVersion = selectedMc.getId();
-
- List loaderOptions = buildLoaderOptions(mcVersion);
- ArrowMenu loaderMenu = new ArrowMenu("Выбор модлоадера для " + mcVersion, loaderOptions);
- int loaderChoice = loaderMenu.show();
-
- if (loaderChoice == -1 || loaderChoice == loaderOptions.size() - 1) return;
-
- String selectedLoader = loaderOptions.get(loaderChoice);
-
- if (selectedLoader.contains("Vanilla")) {
- createVanillaInstance();
- return;
- }
-
- String loaderType;
- if (selectedLoader.contains("Fabric")) {
- loaderType = "fabric";
- } else if (selectedLoader.contains("NeoForge")) {
- loaderType = "neoforge";
- } else {
- loaderType = "forge";
- }
-
- String loaderVersion;
- if (loaderType.equals("fabric")) {
- loaderVersion = askFabricLoaderVersion();
- } else if (loaderType.equals("neoforge")) {
- loaderVersion = askNeoForgeVersion(mcVersion);
- } else {
- loaderVersion = askForgeVersion(mcVersion);
- }
-
- if (loaderVersion == null) return;
-
- String packName = askPackName();
- if (packName == null) return;
-
- if (InstanceManager.getInstance(packName) != null) {
- System.out.println(ZAnsi.brightRed("Сборка с таким именем уже существует!"));
- ConsoleUtils.pause();
- return;
- }
-
- InstanceManager.createInstanceFolder(packName);
- Instance newInstance = InstanceManager.getInstance(packName);
-
- MinecraftLib lib = new MinecraftLib(newInstance);
-
- boolean success;
- if (loaderType.equals("fabric")) {
- success = lib.installFabric(mcVersion, loaderVersion);
- } else if (loaderType.equals("neoforge")) {
- success = lib.installNeoForge(mcVersion, loaderVersion);
- } else {
- success = lib.installForge(mcVersion, loaderVersion);
- }
-
- if (success) {
- System.out.println(ZAnsi.brightGreen("\n[OK] Сборка '" + packName + "' успешно установлена!"));
- } else {
- System.out.println(ZAnsi.brightRed("\n[FAIL] Не удалось установить сборку."));
- }
-
- ConsoleUtils.pause();
- }
-
- private List buildLoaderOptions(String mcVersion) {
- List options = new ArrayList<>();
-
- if (isFabricSupported(mcVersion)) options.add("Fabric");
- if (isNeoForgeSupported(mcVersion)) options.add("NeoForge");
- if (isForgeSupported(mcVersion)) options.add("Forge");
- options.add("Vanilla");
- options.add("Назад");
-
- return options;
- }
-
- private boolean isFabricSupported(String version) {
- return version.matches("^1\\.(1[4-9]|[2-9]\\d).*");
- }
-
- private boolean isForgeSupported(String version) {
- if (version.matches("^1\\.2[2-9].*") || version.matches("^\\d{2}.*")) return false;
- return version.matches("^1\\.(1[2-9]|[2-9]\\d).*") ||
- version.matches("^1\\.20.*") || version.matches("^1\\.21.*");
- }
-
- private boolean isNeoForgeSupported(String version) {
- return version.matches("^1\\.20\\.[1-9].*") ||
- version.matches("^1\\.21.*") ||
- version.matches("^\\d{2}\\..*");
- }
-
- private String askFabricLoaderVersion() throws Exception {
- System.out.println(ZAnsi.cyan("Получение списка версий Fabric Loader..."));
- List versions = ZHttpClient.getFabricLoaderVersions();
-
- List options = versions.stream()
- .limit(30)
- .map(v -> "Fabric Loader " + v)
- .collect(Collectors.toList());
- options.add("Назад");
-
- ArrowMenu menu = new ArrowMenu("Выбор версии Fabric Loader", options);
- int choice = menu.show();
-
- if (choice == -1 || choice == options.size() - 1) return null;
- return versions.get(choice);
- }
-
- private String askForgeVersion(String mcVersion) throws Exception {
- System.out.println(ZAnsi.cyan("Получение списка версий Forge для " + mcVersion + "..."));
-
- List allForgeVersions = getAllForgeVersions();
-
- List compatibleVersions = allForgeVersions.stream()
- .filter(v -> v.startsWith(mcVersion + "-"))
- .map(v -> v.substring(mcVersion.length() + 1))
- .collect(Collectors.toList());
-
- if (compatibleVersions.isEmpty()) {
- System.out.println(ZAnsi.yellow("Не найдено совместимых версий Forge для " + mcVersion));
- ConsoleUtils.pause();
- return null;
- }
-
- List options = compatibleVersions.stream()
- .limit(30)
- .map(v -> "Forge " + v)
- .collect(Collectors.toList());
- options.add("Назад");
-
- ArrowMenu menu = new ArrowMenu("Выбор версии Forge для " + mcVersion, options);
- int choice = menu.show();
-
- if (choice == -1 || choice == options.size() - 1) return null;
-
- return compatibleVersions.get(choice);
- }
-
- private List getAllForgeVersions() throws Exception {
- String xml = ZHttpClient.downloadString("https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml");
-
- List versions = new ArrayList<>();
- int index = 0;
-
- while ((index = xml.indexOf("", index)) != -1) {
- int start = index + 9;
- int end = xml.indexOf("", start);
- if (end == -1) break;
-
- String version = xml.substring(start, end).trim();
- versions.add(version);
- index = end;
- }
-
- versions.sort((a, b) -> b.compareTo(a));
- return versions;
- }
-
- private String askNeoForgeVersion(String mcVersion) throws Exception {
- System.out.println(ZAnsi.cyan("Получение списка версий NeoForge для " + mcVersion + "..."));
-
- List allNeoForgeVersions = getAllNeoForgeVersions();
-
- List compatibleVersions = allNeoForgeVersions.stream()
- .filter(v -> isNeoForgeVersionCompatible(v, mcVersion))
- .collect(Collectors.toList());
-
- if (compatibleVersions.isEmpty()) {
- System.out.println(ZAnsi.yellow("Не найдено совместимых версий NeoForge для " + mcVersion));
- ConsoleUtils.pause();
- return null;
- }
-
- List options = compatibleVersions.stream()
- .limit(30)
- .map(v -> "NeoForge " + v)
- .collect(Collectors.toList());
- options.add("Назад");
-
- ArrowMenu menu = new ArrowMenu("Выбор версии NeoForge для " + mcVersion, options);
- int choice = menu.show();
-
- if (choice == -1 || choice == options.size() - 1) return null;
-
- return compatibleVersions.get(choice);
- }
-
- private boolean isNeoForgeVersionCompatible(String version, String mcVersion) {
- if (mcVersion.equals("1.20.1")) {
- return version.startsWith("47.");
- }
- String majorMinor = mcVersion.replace("1.", "");
- String[] parts = majorMinor.split("\\.");
- int targetMajor = Integer.parseInt(parts[0]);
- return version.startsWith(targetMajor + ".");
- }
-
- private List getAllNeoForgeVersions() throws Exception {
- List versions = new ArrayList<>();
-
- String[] mavenUrls = {
- "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml",
- "https://maven.neoforged.net/releases/net/neoforged/forge/maven-metadata.xml"
- };
-
- for (String mavenUrl : mavenUrls) {
- try {
- String xml = ZHttpClient.downloadString(mavenUrl);
- int index = 0;
- while ((index = xml.indexOf("", index)) != -1) {
- int start = index + 9;
- int end = xml.indexOf("", start);
- if (end == -1) break;
-
- String version = xml.substring(start, end).trim();
- if (!versions.contains(version)) {
- versions.add(version);
- }
- index = end;
- }
- } catch (Exception e) {
- // Skip if one maven doesn't have the artifact
- }
- }
-
- versions.sort((a, b) -> b.compareTo(a));
- return versions;
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/LoginMenu.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/LoginMenu.java
deleted file mode 100644
index 6a106a6..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/LoginMenu.java
+++ /dev/null
@@ -1,207 +0,0 @@
-package me.sashegdev.zernmc.launcher.menu;
-
-import me.sashegdev.zernmc.launcher.auth.AuthManager;
-import me.sashegdev.zernmc.launcher.auth.AuthManager.AuthResult;
-import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
-import me.sashegdev.zernmc.launcher.utils.ConsoleUtils;
-import me.sashegdev.zernmc.launcher.utils.Input;
-import me.sashegdev.zernmc.launcher.utils.ZAnsi;
-
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Экран входа/регистрации.
- * Показывается при старте лаунчера, если нет сохранённой сессии.
- *
- * show() возвращает true — пользователь вошёл/зарегистрировался
- * false — пользователь выбрал выход из лаунчера
- */
-public class LoginMenu {
-
- /**
- * Главный экран выбора действия.
- */
- public boolean show() throws IOException {
- while (true) {
- ConsoleUtils.clearScreen();
- printBanner();
-
- List options = List.of(
- "Войти в аккаунт",
- "Создать аккаунт",
- "Выйти из лаунчера"
- );
-
- ArrowMenu menu = new ArrowMenu("Добро пожаловать в ZernMC!", options);
- int choice = menu.show();
-
- if (choice == -1 || choice == 2) return false;
-
- boolean success = switch (choice) {
- case 0 -> doLogin();
- case 1 -> doRegister();
- default -> false;
- };
-
- if (success) return true;
- // Если не успех — покажем меню снова (ошибка уже напечатана внутри методов)
- }
- }
-
- /**
- * Показывается когда пользователь уже вошёл — предлагает выйти из аккаунта.
- */
- public void showAccountMenu() throws IOException {
- ConsoleUtils.clearScreen();
-
- System.out.println(ZAnsi.header("=== Аккаунт ==="));
- System.out.println();
- System.out.println(ZAnsi.white(" Игрок: ") + ZAnsi.brightGreen(AuthManager.getUsername()));
- System.out.println(ZAnsi.white(" UUID: ") + ZAnsi.cyan(AuthManager.getUuid()));
- System.out.println();
-
- List options = List.of(
- "Выйти из аккаунта",
- "Назад"
- );
-
- ArrowMenu menu = new ArrowMenu("Управление аккаунтом", options);
- int choice = menu.show();
-
- if (choice == 0) {
- AuthManager.logout();
- System.out.println(ZAnsi.yellow("Вы вышли из аккаунта."));
- ConsoleUtils.pause();
- }
- }
-
- // ====================== ПРИВАТНЫЕ МЕТОДЫ ======================
-
- private boolean doLogin() throws IOException {
- ConsoleUtils.clearScreen();
- printBanner();
- System.out.println(ZAnsi.cyan(" [ Вход в аккаунт ]"));
- System.out.println();
-
- String username = Input.readLine(ZAnsi.white(" Имя пользователя: "));
- if (username.isEmpty()) return false;
-
- String password = readPassword(" Пароль: ");
- if (password.isEmpty()) return false;
-
- System.out.println();
- System.out.print(ZAnsi.cyan(" Выполняем вход..."));
-
- AuthResult result = AuthManager.login(username, password);
-
- if (result.success) {
- System.out.println("\r" + ZAnsi.brightGreen(" Добро пожаловать, " + AuthManager.getUsername() + "! "));
- ConsoleUtils.pause();
- return true;
- } else {
- System.out.println("\r" + ZAnsi.brightRed(" Ошибка: " + result.error + " "));
- ConsoleUtils.pause();
- return false;
- }
- }
-
- private boolean doRegister() throws IOException {
- ConsoleUtils.clearScreen();
- printBanner();
- System.out.println(ZAnsi.cyan(" [ Создание аккаунта ]"));
- System.out.println();
- System.out.println(ZAnsi.yellow(" Допустимые символы в имени: a-z, A-Z, 0-9, _"));
- System.out.println(ZAnsi.yellow(" Длина имени: 3-16 символов | Длина пароля: от 6 символов"));
- System.out.println();
-
- String username = Input.readLine(ZAnsi.white(" Имя пользователя: "));
- if (username.isEmpty()) return false;
-
- String password = readPassword(" Пароль: ");
- if (password.isEmpty()) return false;
-
- String confirm = readPassword(" Повторите пароль: ");
- if (!password.equals(confirm)) {
- System.out.println(ZAnsi.brightRed("\n Пароли не совпадают!"));
- ConsoleUtils.pause();
- return false;
- }
-
- System.out.println();
- System.out.print(ZAnsi.cyan(" Создаём аккаунт..."));
-
- AuthResult result = AuthManager.register(username, password);
-
- if (result.success) {
- System.out.println("\r" + ZAnsi.brightGreen(" Аккаунт создан! Добро пожаловать, " + AuthManager.getUsername() + "! "));
- ConsoleUtils.pause();
- return true;
- } else {
- System.out.println("\r" + ZAnsi.brightRed(" Ошибка: " + result.error + " "));
- ConsoleUtils.pause();
- return false;
- }
- }
-
- /**
- * Читаем пароль — стараемся скрыть вывод через Console,
- * если недоступно (IDE/терминал без TTY) — читаем обычным способом.
- */
- private String readPassword(String prompt) throws IOException {
- org.jline.terminal.Terminal passTerminal = org.jline.terminal.TerminalBuilder.builder()
- .system(true)
- .jna(true)
- .build();
-
- passTerminal.enterRawMode();
- passTerminal.writer().print(prompt);
- passTerminal.writer().flush();
-
- StringBuilder password = new StringBuilder();
-
- try {
- while (true) {
- int key = passTerminal.reader().read();
-
- if (key == 27) {
- // Escape sequence — consume remaining bytes (arrow keys, etc.)
- int next = passTerminal.reader().read(50);
- if (next == 91) { // '[' — arrow key sequence
- passTerminal.reader().read(50); // consume 'A'/'B'/'C'/'D'
- }
- continue;
- }
-
- if (key == 13 || key == 10) { // Enter
- passTerminal.writer().println();
- break;
- } else if (key == 127 || key == 8) { // Backspace
- if (password.length() > 0) {
- password.setLength(password.length() - 1);
- passTerminal.writer().print("\b \b");
- passTerminal.writer().flush();
- }
- } else if (key == 3) { // Ctrl+C
- passTerminal.writer().println();
- System.exit(0);
- } else if (key >= 32 && key < 127) { // Printable characters
- password.append((char) key);
- passTerminal.writer().print('*');
- passTerminal.writer().flush();
- }
- }
- } finally {
- passTerminal.close();
- }
-
- return password.toString();
- }
-
- private void printBanner() {
- System.out.println(ZAnsi.header("╔══════════════════════════════╗"));
- System.out.println(ZAnsi.header("║ ZernMC Launcher ║"));
- System.out.println(ZAnsi.header("╚══════════════════════════════╝"));
- System.out.println();
- }
-}
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/ServerCheckMenu.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/ServerCheckMenu.java
deleted file mode 100644
index 51e113b..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/ServerCheckMenu.java
+++ /dev/null
@@ -1,141 +0,0 @@
-package me.sashegdev.zernmc.launcher.menu;
-
-import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
-import me.sashegdev.zernmc.launcher.utils.ConsoleUtils;
-import me.sashegdev.zernmc.launcher.utils.ZAnsi;
-import me.sashegdev.zernmc.launcher.utils.ZHttpClient;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.time.Duration;
-import java.util.List;
-
-public class ServerCheckMenu {
-
- public void show() throws IOException {
- while (true) {
- ConsoleUtils.clearScreen();
- System.out.println(ZAnsi.header("Диагностика подключения"));
-
- List options = List.of(
- "Проверить подключение к ZernMC серверу",
- "Проверить доступ к Mojang (Minecraft)",
- "Проверить доступ к Fabric Meta",
- "Проверить доступ к Forge Maven",
- "Назад в главное меню"
- );
-
- ArrowMenu menu = new ArrowMenu("Выберите проверку", options);
- int choice = menu.show();
-
- if (choice == -1 || choice == 4) {
- return;
- }
-
- ConsoleUtils.clearScreen();
-
- switch (choice) {
- case 0 -> checkZernServer();
- case 1 -> checkMojang();
- case 2 -> checkFabric();
- case 3 -> checkForge();
- }
-
- ConsoleUtils.pause();
- }
- }
-
- private void checkZernServer() {
- System.out.println(ZAnsi.cyan("Проверка подключения к ZernMC серверу..."));
-
- try {
- String response = ZHttpClient.get("/health");
- System.out.println(ZAnsi.brightGreen("[OK] ZernMC сервер успешно подключён!"));
- System.out.println(ZAnsi.white("Ответ сервера: ") + response);
- } catch (Exception e) {
- System.out.println(ZAnsi.brightRed("[FAIL] Не удалось подключиться к ZernMC серверу"));
- System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
- }
- }
-
- private void checkMojang() {
- System.out.println(ZAnsi.cyan("Проверка доступа к Mojang..."));
-
- try {
- HttpClient client = HttpClient.newBuilder()
- .connectTimeout(Duration.ofSeconds(10))
- .build();
-
- HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"))
- .GET()
- .build();
-
- HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
-
- if (response.statusCode() == 200) {
- System.out.println(ZAnsi.brightGreen("[OK] Mojang доступен"));
- } else {
- System.out.println(ZAnsi.brightRed("[FAIL] Mojang вернул код " + response.statusCode()));
- }
- } catch (Exception e) {
- System.out.println(ZAnsi.brightRed("[FAIL] Нет доступа к Mojang"));
- System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
- }
- }
-
- private void checkFabric() {
- System.out.println(ZAnsi.cyan("Проверка доступа к Fabric Meta..."));
-
- try {
- HttpClient client = HttpClient.newBuilder()
- .connectTimeout(Duration.ofSeconds(10))
- .build();
-
- HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create("https://meta.fabricmc.net/v2/versions"))
- .GET()
- .build();
-
- HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
-
- if (response.statusCode() == 200) {
- System.out.println(ZAnsi.brightGreen("[OK] Fabric Meta доступен"));
- } else {
- System.out.println(ZAnsi.brightRed("[FAIL] Fabric Meta вернул код " + response.statusCode()));
- }
- } catch (Exception e) {
- System.out.println(ZAnsi.brightRed("[FAIL] Нет доступа к Fabric Meta"));
- System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
- }
- }
-
- private void checkForge() {
- System.out.println(ZAnsi.cyan("Проверка доступа к Forge Maven..."));
-
- try {
- HttpClient client = HttpClient.newBuilder()
- .connectTimeout(Duration.ofSeconds(10))
- .build();
-
- HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create("https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml"))
- .GET()
- .build();
-
- HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
-
- if (response.statusCode() == 200) {
- System.out.println(ZAnsi.brightGreen("[OK] Forge Maven доступен"));
- } else {
- System.out.println(ZAnsi.brightRed("[FAIL] Forge Maven вернул код " + response.statusCode()));
- }
- } catch (Exception e) {
- System.out.println(ZAnsi.brightRed("[FAIL] Нет доступа к Forge Maven"));
- System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
- }
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/SettingsMenu.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/SettingsMenu.java
deleted file mode 100644
index 7c16845..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/SettingsMenu.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package me.sashegdev.zernmc.launcher.menu;
-
-import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
-import me.sashegdev.zernmc.launcher.utils.Config;
-import me.sashegdev.zernmc.launcher.utils.ConsoleUtils;
-import me.sashegdev.zernmc.launcher.utils.Input;
-import me.sashegdev.zernmc.launcher.utils.ZAnsi;
-
-import java.io.IOException;
-import java.util.List;
-
-public class SettingsMenu {
-
- public void show() throws IOException {
- List options = List.of(
- "Настроить путь к Java",
- "Настроить выделенную память (RAM)",
- "Дополнительные JVM параметры",
- "Назад в главное меню"
- );
-
- ArrowMenu menu = new ArrowMenu("Настройки лаунчера", options);
- int choice = menu.show();
-
- if (choice == -1 || choice == 3) return;
-
- ConsoleUtils.clearScreen();
-
- switch (choice) {
- case 0 -> configureJava();
- case 1 -> configureRam();
- case 2 -> configureJvmArgs();
- }
-
- ConsoleUtils.pause();
- }
-
- private void configureJava() {
- System.out.println(ZAnsi.cyan("Путь к Java:"));
- System.out.println(" " + Config.getJreDir().toAbsolutePath());
- System.out.println(ZAnsi.white("\nJava будет искаться автоматически в папке ~/.zernmc/jre/"));
- System.out.println("Если нужно — положите туда свою версию Java.");
- }
-
- private void configureRam() {
- System.out.println(ZAnsi.cyan("Настройка выделенной памяти"));
- System.out.println(Config.getRamInfo());
-
- int newRam = Input.readInt(
- ZAnsi.white("\nВведите новое значение RAM в MB (или 0 для отмены): "),
- 0, 32768
- );
-
- if (newRam == 0) {
- System.out.println(ZAnsi.yellow("Настройка отменена."));
- return;
- }
-
- Config.setMaxMemory(newRam);
- System.out.println(ZAnsi.brightGreen("Выделенная память изменена на " + newRam + " MB"));
- }
-
- private void configureJvmArgs() {
- System.out.println(ZAnsi.yellow("Дополнительные JVM параметры"));
- System.out.println("Пока в разработке.");
- System.out.println("В будущем здесь будет список предустановленных оптимизаций.");
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/UpdateMenu.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/UpdateMenu.java
deleted file mode 100644
index 41f8b20..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/menu/UpdateMenu.java
+++ /dev/null
@@ -1,152 +0,0 @@
-package me.sashegdev.zernmc.launcher.menu;
-
-import me.sashegdev.zernmc.launcher.minecraft.Instance;
-import me.sashegdev.zernmc.launcher.minecraft.InstanceManager;
-import me.sashegdev.zernmc.launcher.minecraft.PackDownloader;
-import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
-import me.sashegdev.zernmc.launcher.utils.ConsoleUtils;
-import me.sashegdev.zernmc.launcher.utils.Input;
-import me.sashegdev.zernmc.launcher.utils.ZAnsi;
-import me.sashegdev.zernmc.launcher.utils.ZHttpClient;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class UpdateMenu {
-
- public void show() throws IOException {
- List options = List.of(
- "Проверить обновления сборки (модпака)",
- "Проверить обновления лаунчера",
- "Назад в главное меню"
- );
-
- ArrowMenu menu = new ArrowMenu("Проверка обновлений", options);
- int choice = menu.show();
-
- if (choice == -1 || choice == 2) return;
-
- ConsoleUtils.clearScreen();
-
- if (choice == 0) {
- try {
- checkPackUpdates();
- } catch (Exception e) {
- System.out.println(ZAnsi.brightRed("Ошибка: " + e.getMessage()));
- e.printStackTrace();
- ConsoleUtils.pause();
- }
- } else {
- checkLauncherUpdates();
- }
- }
-
- private void checkPackUpdates() throws Exception {
- System.out.println(ZAnsi.cyan("Проверка обновлений сборок..."));
-
- List instances = InstanceManager.getAllInstances();
- List serverInstances = instances.stream()
- .filter(Instance::isServerPack)
- .collect(Collectors.toList());
-
- if (serverInstances.isEmpty()) {
- System.out.println(ZAnsi.yellow("Нет сборок, установленных с сервера."));
- ConsoleUtils.pause();
- return;
- }
-
- System.out.println(ZAnsi.cyan("\nПроверка обновлений для серверных сборок:\n"));
-
- boolean hasUpdates = false;
- List updatableInstances = new ArrayList<>();
-
- for (Instance instance : serverInstances) {
- PackDownloader downloader = new PackDownloader(instance);
-
- try {
- boolean hasUpdate = downloader.checkForUpdates(instance.getServerPackName());
- if (hasUpdate) {
- System.out.println(ZAnsi.yellow(instance.getName() + " - Есть обновление!"));
- updatableInstances.add(instance);
- hasUpdates = true;
- } else {
- System.out.println(ZAnsi.green(instance.getName() + " - Актуальна"));
- }
- } catch (Exception e) {
- System.out.println(ZAnsi.red(instance.getName() + " - Ошибка проверки: " + e.getMessage()));
- }
- }
-
- if (!hasUpdates) {
- System.out.println(ZAnsi.green("\nВсе сборки актуальны!"));
- ConsoleUtils.pause();
- return;
- }
-
- // Предлагаем обновить каждую сборку отдельно
- for (Instance instance : updatableInstances) {
- System.out.println(ZAnsi.brightYellow("\nОбновить сборку '" + instance.getName() + "'?"));
- if (Input.confirm("Обновить")) {
- System.out.println(ZAnsi.cyan("Обновление " + instance.getName() + "..."));
- PackDownloader downloader = new PackDownloader(instance);
-
- try {
- boolean success = downloader.updatePack(instance.getServerPackName());
- if (success) {
- System.out.println(ZAnsi.brightGreen(instance.getName() + " обновлен"));
- } else {
- System.out.println(ZAnsi.brightRed(instance.getName() + " не удалось обновить"));
- }
- } catch (Exception e) {
- System.out.println(ZAnsi.brightRed(instance.getName() + ": " + e.getMessage()));
- }
- } else {
- System.out.println(ZAnsi.yellow(" Пропущено: " + instance.getName()));
- }
- }
-
- ConsoleUtils.pause();
- }
-
- private void checkLauncherUpdates() {
- System.out.println(ZAnsi.cyan("Проверка обновлений лаунчера..."));
-
- try {
- String json = ZHttpClient.getLauncherVersionInfo();
- String serverVersion = extractVersion(json);
- String currentVersion = me.sashegdev.zernmc.launcher.utils.Version.getCurrentVersion();
-
- System.out.println(ZAnsi.white("Текущая версия: ") + currentVersion);
- System.out.println(ZAnsi.white("Версия на сервере: ") + serverVersion);
-
- if (me.sashegdev.zernmc.launcher.utils.Version.isNewer(currentVersion, serverVersion)) {
- System.out.println(ZAnsi.brightYellow("\nДоступна новая версия!"));
- if (Input.confirm("Обновить лаунчер?")) {
- // Обновление будет при следующем запуске
- System.out.println(ZAnsi.green("Лаунчер будет обновлен при следующем запуске."));
- }
- } else {
- System.out.println(ZAnsi.brightGreen("Лаунчер актуален."));
- }
- } catch (Exception e) {
- System.out.println(ZAnsi.yellow("Не удалось проверить обновления лаунчера."));
- System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
- }
-
- ConsoleUtils.pause();
- }
-
- private String extractVersion(String json) {
- try {
- int start = json.indexOf("\"version\"");
- if (start == -1) return "unknown";
- start = json.indexOf("\"", start + 9) + 1;
- int end = json.indexOf("\"", start);
- return json.substring(start, end);
- } catch (Exception e) {
- return "unknown";
- }
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/Instance.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/Instance.java
deleted file mode 100644
index 7c37175..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/Instance.java
+++ /dev/null
@@ -1,172 +0,0 @@
-package me.sashegdev.zernmc.launcher.minecraft;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-
-
-// ЭТОТ КЛАСС РАБОТАЕТ НЕ ТРОГАТЬ ТОТ КТО БУДЕТ ЧИТАТЬ (на момент 1.0.2)
-public class Instance {
- private final String name;
- private final Path path;
-
- private String minecraftVersion;
- private String loaderType; // vanilla, fabric, forge, neoforge
- private String loaderVersion;
- private String assetIndex;
- private boolean isServerPack; // флаг, что это сборка с сервера
- private int serverVersion; // версия сборки на сервере
- private String serverPackName; // имя пака на сервере
- private String fabricVersionId;
-
- private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
-
- public Instance(String name, Path path) {
- this.name = name;
- this.path = path;
- loadMetadata();
- }
-
- public String getName() { return name; }
- public Path getPath() { return path; }
-
- public String getMinecraftVersion() { return minecraftVersion; }
- public void setMinecraftVersion(String minecraftVersion) {
- this.minecraftVersion = minecraftVersion;
- saveMetadata();
- }
-
- public String getLoaderType() { return loaderType != null ? loaderType : "vanilla"; }
- public void setLoaderType(String loaderType) {
- this.loaderType = loaderType;
- saveMetadata();
- }
-
- public String getLoaderVersion() { return loaderVersion; }
- public void setLoaderVersion(String loaderVersion) {
- this.loaderVersion = loaderVersion;
- saveMetadata();
- }
-
- public String getAssetIndex() {
- return assetIndex != null ? assetIndex : minecraftVersion; // fallback для старых сборок
- }
-
- public void setAssetIndex(String assetIndex) {
- this.assetIndex = assetIndex;
- saveMetadata();
- }
-
- public String getFabricVersionId() {
- return fabricVersionId;
- }
-
- public void setFabricVersionId(String fabricVersionId) {
- this.fabricVersionId = fabricVersionId;
- saveMetadata();
- }
-
-
- public boolean isServerPack() {
- return isServerPack;
- }
-
- public void setServerPack(boolean serverPack) {
- this.isServerPack = serverPack;
- saveMetadata();
- }
-
- public int getServerVersion() {
- return serverVersion;
- }
-
- public void setServerVersion(int serverVersion) {
- this.serverVersion = serverVersion;
- saveMetadata();
- }
-
- public String getServerPackName() {
- return serverPackName;
- }
-
- public void setServerPackName(String serverPackName) {
- this.serverPackName = serverPackName;
- saveMetadata();
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(name);
- if (minecraftVersion != null) {
- sb.append(" [").append(minecraftVersion);
- if (!"vanilla".equalsIgnoreCase(getLoaderType())) {
- sb.append(" + ").append(getLoaderType());
- if (loaderVersion != null) sb.append(" ").append(loaderVersion);
- }
- sb.append("]");
- if (isServerPack) {
- sb.append("v").append(serverVersion);
- }
- } else {
- sb.append(" [?]");
- }
- return sb.toString();
- }
-
- // ====================== Метаданные ======================
- private void loadMetadata() {
- Path metaFile = path.resolve("instance.json");
- if (!Files.exists(metaFile)) return;
- try {
- String json = Files.readString(metaFile);
- InstanceMeta meta = GSON.fromJson(json, InstanceMeta.class);
- this.minecraftVersion = meta.minecraftVersion;
- this.loaderType = meta.loaderType;
- this.loaderVersion = meta.loaderVersion;
- this.assetIndex = meta.assetIndex;
- this.isServerPack = meta.isServerPack;
- this.serverVersion = meta.serverVersion;
- this.serverPackName = meta.serverPackName;
- } catch (Exception ignored) {}
- }
-
- private void saveMetadata() {
- Path metaFile = path.resolve("instance.json");
- InstanceMeta meta = new InstanceMeta(
- minecraftVersion, loaderType, loaderVersion, assetIndex,
- isServerPack, serverVersion, serverPackName
- );
- try {
- Files.writeString(metaFile, GSON.toJson(meta));
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- private static class InstanceMeta {
- String minecraftVersion;
- String loaderType;
- String loaderVersion;
- String assetIndex;
- boolean isServerPack = false;
- int serverVersion = 0;
- String serverPackName;
-
-
- public InstanceMeta(String minecraftVersion, String loaderType,
- String loaderVersion, String assetIndex,
- boolean isServerPack, int serverVersion,
- String serverPackName) {
- this.minecraftVersion = minecraftVersion;
- this.loaderType = loaderType;
- this.loaderVersion = loaderVersion;
- this.assetIndex = assetIndex;
- this.isServerPack = isServerPack;
- this.serverVersion = serverVersion;
- this.serverPackName = serverPackName;
- }
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/InstanceManager.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/InstanceManager.java
deleted file mode 100644
index 9d16fbe..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/InstanceManager.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package me.sashegdev.zernmc.launcher.minecraft;
-
-import me.sashegdev.zernmc.launcher.utils.Config;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class InstanceManager {
-
- private static final Path INSTANCES_DIR = Config.getInstancesDir();
-
- public static List getAllInstances() throws IOException {
- if (!Files.exists(INSTANCES_DIR)) {
- Files.createDirectories(INSTANCES_DIR);
- return List.of();
- }
-
- return Files.list(INSTANCES_DIR)
- .filter(Files::isDirectory)
- .map(path -> new Instance(path.getFileName().toString(), path))
- .collect(Collectors.toList());
- }
-
- public static Instance getInstance(String name) {
- Path instancePath = INSTANCES_DIR.resolve(name);
- if (Files.exists(instancePath) && Files.isDirectory(instancePath)) {
- return new Instance(name, instancePath);
- }
- return null;
- }
-
- public static boolean deleteInstance(String instanceName) {
- if (instanceName == null || instanceName.isBlank()) {
- return false;
- }
-
- Path instancePath = INSTANCES_DIR.resolve(instanceName);
-
- if (!Files.exists(instancePath)) {
- return false;
- }
-
- try {
- // Рекурсивно удаляем всю папку сборки
- Files.walk(instancePath)
- .sorted((a, b) -> b.compareTo(a)) // удаляем снизу вверх
- .forEach(path -> {
- try {
- Files.deleteIfExists(path);
- } catch (IOException e) {
- System.err.println("Не удалось удалить: " + path);
- }
- });
- return true;
- } catch (IOException e) {
- e.printStackTrace();
- return false;
- }
- }
-
- public static boolean createInstanceFolder(String name) throws IOException {
- Path path = INSTANCES_DIR.resolve(name);
- if (Files.exists(path)) {
- return false;
- }
- Files.createDirectories(path);
- return true;
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/MinecraftLib.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/MinecraftLib.java
deleted file mode 100644
index 9593357..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/MinecraftLib.java
+++ /dev/null
@@ -1,196 +0,0 @@
-package me.sashegdev.zernmc.launcher.minecraft;
-
-import me.sashegdev.zernmc.launcher.minecraft.installer.FabricInstaller;
-import me.sashegdev.zernmc.launcher.minecraft.installer.ForgeInstaller;
-import me.sashegdev.zernmc.launcher.minecraft.installer.NeoForgeInstaller;
-import me.sashegdev.zernmc.launcher.minecraft.installer.VersionInstaller;
-import me.sashegdev.zernmc.launcher.minecraft.launch.LaunchCommandBuilder;
-import me.sashegdev.zernmc.launcher.minecraft.model.LaunchOptions;
-import me.sashegdev.zernmc.launcher.utils.ConsoleUtils;
-import me.sashegdev.zernmc.launcher.utils.ZAnsi;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-
-public class MinecraftLib {
-
- private final Instance instance;
-
- public MinecraftLib(Instance instance) {
- this.instance = instance;
- }
-
- //Установка
- public boolean installMinecraft(String versionId) throws Exception {
- VersionInstaller installer = new VersionInstaller(instance.getPath());
-
- String assetIndex = installer.install(versionId); // ← теперь возвращается String
-
- if (assetIndex != null && !assetIndex.isEmpty()) {
- instance.setMinecraftVersion(versionId);
- instance.setAssetIndex(assetIndex); // ← сохраняем правильный индекс!
- instance.setLoaderType("vanilla");
- return true;
- }
- return false;
- }
-
- public boolean installForge(String minecraftVersion, String forgeVersion) throws Exception {
- ForgeInstaller installer = new ForgeInstaller(instance);
- return installer.install(minecraftVersion, forgeVersion);
- }
-
- public boolean installNeoForge(String minecraftVersion, String neoforgeVersion) throws Exception {
- NeoForgeInstaller installer = new NeoForgeInstaller(instance);
- return installer.install(minecraftVersion, neoforgeVersion);
- }
-
- public boolean installFabric(String minecraftVersion, String loaderVersion) throws Exception {
- FabricInstaller installer = new FabricInstaller(instance);
- boolean success = installer.install(minecraftVersion, loaderVersion);
-
- if (success) {
- // Сохраняем информацию в Instance
- instance.setMinecraftVersion(minecraftVersion);
- instance.setLoaderType("fabric");
- instance.setLoaderVersion(loaderVersion);
- }
- return success;
- }
-
- /**
- * Полная установка сборки (vanilla + loader + моды)
- * Пока заглушка — будем расширять
- */
- public boolean installPack(String packName, String minecraftVersion, String loaderType, String loaderVersion) throws Exception {
- System.out.println(ZAnsi.cyan("Начинается полная установка сборки: " + packName));
-
- // 1. Устанавливаем Minecraft
- boolean mcInstalled = installMinecraft(minecraftVersion);
- if (!mcInstalled) {
- System.out.println(ZAnsi.brightRed("Не удалось установить Minecraft " + minecraftVersion));
- return false;
- }
-
- // 2. Устанавливаем лоадер
- if ("fabric".equalsIgnoreCase(loaderType)) {
- boolean fabricInstalled = installFabric(minecraftVersion, loaderVersion);
- if (!fabricInstalled) {
- System.out.println(ZAnsi.brightRed("Не удалось установить Fabric"));
- return false;
- }
- } else if ("forge".equalsIgnoreCase(loaderType)) {
- boolean forgeInstalled = installForge(minecraftVersion, loaderVersion);
- if (!forgeInstalled) {
- System.out.println(ZAnsi.brightRed("Не удалось установить Forge"));
- return false;
- }
- } else if ("neoforge".equalsIgnoreCase(loaderType)) {
- boolean neoforgeInstalled = installNeoForge(minecraftVersion, loaderVersion);
- if (!neoforgeInstalled) {
- System.out.println(ZAnsi.brightRed("Не удалось установить NeoForge"));
- return false;
- }
- }
-
- // 3. В будущем здесь будет diff и скачивание модов
-
- System.out.println(ZAnsi.brightGreen("Базовая установка сборки завершена!"));
- return true;
- }
-
- //Запуск
- public void launch(LaunchOptions options) throws Exception {
- System.out.println(ZAnsi.brightGreen("Запуск сборки: " + instance.getName()));
- cleanupOldLoaders();
-
- LaunchCommandBuilder builder = new LaunchCommandBuilder(instance);
- List command = builder.build(options);
-
- System.out.println(ZAnsi.cyan("Команда запуска (" + command.size() + " аргументов):"));
- command.forEach(arg -> System.out.println(" " + arg));
-
- ProcessBuilder pb = new ProcessBuilder(command);
- pb.directory(instance.getPath().toFile());
- pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
- pb.redirectError(ProcessBuilder.Redirect.INHERIT);
- pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
-
- System.out.println(ZAnsi.brightGreen("\nЗапускаем Minecraft...\n"));
- ConsoleUtils.clearScreen();
-
- Process process = pb.start();
- int exitCode = process.waitFor();
-
- System.out.println(ZAnsi.yellow("\nMinecraft завершился с кодом: " + exitCode));
- }
-
- private void safeDeleteDirectory(Path dir) {
- try {
- Files.walk(dir)
- .sorted((a, b) -> b.compareTo(a))
- .forEach(p -> {
- try { Files.deleteIfExists(p); }
- catch (IOException ignored) {}
- });
- } catch (IOException ignored) {}
- }
-
- private void deleteOldVersionDirs(Path versionsDir, String keepVersion) throws IOException {
- if (!Files.exists(versionsDir)) return;
-
- try (var stream = Files.walk(versionsDir)) {
- stream.filter(Files::isDirectory)
- .filter(dir -> dir.getFileName().toString().contains("fabric-loader") ||
- dir.getFileName().toString().contains("forge") ||
- dir.getFileName().toString().contains("neoforge"))
- .filter(dir -> !dir.getFileName().toString().contains(keepVersion))
- .forEach(this::safeDeleteDirectory);
- }
- }
-
- private void deleteAllExcept(Path baseDir, String keepVersion) throws IOException {
- if (!Files.exists(baseDir)) return;
-
- try (var stream = Files.walk(baseDir)) {
- stream.filter(Files::isDirectory)
- .filter(dir -> {
- String name = dir.getFileName().toString();
- return name.contains(".") && !name.contains(keepVersion);
- })
- .forEach(this::safeDeleteDirectory);
- }
- }
-
- private void cleanupOldLoaders() throws IOException {
- String loaderType = instance.getLoaderType().toLowerCase();
- String currentLoaderVer = instance.getLoaderVersion();
-
- if (currentLoaderVer == null) return;
-
- System.out.println(ZAnsi.yellow("Выполняем очистку старых версий лоадера..."));
-
- // Удаляем все старые fabric-loader / forge
- Path libraries = instance.getPath().resolve("libraries");
-
- if ("fabric".equals(loaderType)) {
- deleteAllExcept(libraries.resolve("net/fabricmc/fabric-loader"), currentLoaderVer);
- } else if ("forge".equals(loaderType)) {
- deleteAllExcept(libraries.resolve("net/minecraftforge/forge"), currentLoaderVer);
- } else if ("neoforge".equals(loaderType)) {
- deleteAllExcept(libraries.resolve("net/neoforged/neoforge"), currentLoaderVer);
- }
-
- // Также чистим versions/ от старых fabric/forge версий
- Path versionsDir = instance.getPath().resolve("versions");
- deleteOldVersionDirs(versionsDir, currentLoaderVer);
- }
-
-
-
- public Instance getInstance() {
- return instance;
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/PackDownloader.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/PackDownloader.java
deleted file mode 100644
index 2aae36d..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/PackDownloader.java
+++ /dev/null
@@ -1,567 +0,0 @@
-package me.sashegdev.zernmc.launcher.minecraft;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-
-import me.sashegdev.zernmc.launcher.auth.AuthManager;
-import me.sashegdev.zernmc.launcher.utils.ProgressBar;
-import me.sashegdev.zernmc.launcher.utils.ZAnsi;
-import me.sashegdev.zernmc.launcher.utils.ZHttpClient;
-
-import java.io.*;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.MessageDigest;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.*;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public class PackDownloader {
-
- private final Instance instance;
- private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
- private final HttpClient httpClient = HttpClient.newHttpClient();
- //private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
-
- public PackDownloader(Instance instance) {
- this.instance = instance;
- }
-
- /**
- * Получить список доступных паков с сервера
- */
- public List getAvailablePacks() throws Exception {
- String accessToken = AuthManager.getAccessToken();
- if (accessToken == null) {
- throw new IOException("Не авторизован. Требуется проходка для просмотра сборок.");
- }
- if (!AuthManager.canViewPacks()) {
- throw new IOException("Для просмотра сборок требуется активная проходка");
- }
-
- // Используем HttpURLConnection для GET с авторизацией
- java.net.HttpURLConnection connection = null;
- try {
- java.net.URL url = new java.net.URL(ZHttpClient.getBaseUrl() + "/packs");
- connection = (java.net.HttpURLConnection) url.openConnection();
- connection.setRequestMethod("GET");
- connection.setRequestProperty("Accept", "application/json");
- connection.setRequestProperty("Authorization", "Bearer " + accessToken);
- connection.setConnectTimeout(15000);
- connection.setReadTimeout(15000);
-
- int responseCode = connection.getResponseCode();
-
- if (responseCode == 403) {
- throw new IOException("Для просмотра сборок требуется активная проходка");
- }
-
- StringBuilder response = new StringBuilder();
- 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"))) {
- String line;
- while ((line = reader.readLine()) != null) {
- response.append(line);
- }
- }
-
- if (responseCode != 200) {
- throw new IOException("HTTP " + responseCode);
- }
-
- return parsePacksResponse(response.toString());
-
- } finally {
- if (connection != null) {
- connection.disconnect();
- }
- }
- }
-
- private List parsePacksResponse(String responseBody) {
- JsonObject root = JsonParser.parseString(responseBody).getAsJsonObject();
- JsonArray packsArray = root.getAsJsonArray("packs");
- List result = new ArrayList<>();
-
- for (JsonElement elem : packsArray) {
- JsonObject pack = elem.getAsJsonObject();
-
- if (pack.has("error") || (pack.has("status") && "not_scanned".equals(pack.get("status").getAsString()))) {
- continue;
- }
-
- try {
- String name = pack.get("name").getAsString();
- int version = pack.has("version") ? pack.get("version").getAsInt() : 0;
- String minecraftVersion = pack.has("minecraft_version") ? pack.get("minecraft_version").getAsString() : "unknown";
- String loaderType = pack.has("loader_type") ? pack.get("loader_type").getAsString() : "vanilla";
- String loaderVersion = pack.has("loader_version") && !pack.get("loader_version").isJsonNull()
- ? pack.get("loader_version").getAsString() : "";
- int filesCount = pack.has("files_count") ? pack.get("files_count").getAsInt() : 0;
-
- LocalDateTime updatedAt = null;
- if (pack.has("updated_at") && !pack.get("updated_at").isJsonNull()) {
- try {
- updatedAt = LocalDateTime.parse(pack.get("updated_at").getAsString(),
- DateTimeFormatter.ISO_DATE_TIME);
- } catch (Exception ignored) {}
- }
-
- result.add(new ServerPack(name, version, minecraftVersion, loaderType,
- loaderVersion, updatedAt, filesCount));
- } catch (Exception e) {
- System.err.println("Ошибка парсинга пака: " + e.getMessage());
- }
- }
-
- return result;
- }
-
- /**
- * Получить манифест пака
- */
- public PackManifest getPackManifest(String packName) throws Exception {
- String response = ZHttpClient.get("/pack/" + packName);
- return gson.fromJson(response, PackManifest.class);
- }
-
- /**
- * Установить или обновить сборку с сервера
- */
- public boolean installOrUpdatePack(String packName, ServerPack serverPack) throws Exception {
- System.out.println(ZAnsi.cyan("Установка сборки " + packName + " с сервера..."));
-
- // 1. Получаем манифест
- PackManifest manifest = getPackManifest(packName);
-
- // 2. Сначала устанавливаем Minecraft + Loader через MinecraftLib
- MinecraftLib lib = new MinecraftLib(instance);
-
- System.out.println(ZAnsi.cyan("Установка Minecraft " + manifest.getMinecraftVersion() + "..."));
-
- boolean needsMinecraftInstall = instance.getMinecraftVersion() == null ||
- !instance.getMinecraftVersion().equals(manifest.getMinecraftVersion());
-
- if (needsMinecraftInstall) {
- if ("fabric".equalsIgnoreCase(manifest.getLoaderType())) {
- boolean success = lib.installFabric(manifest.getMinecraftVersion(), manifest.getLoaderVersion());
- if (!success) {
- System.err.println(ZAnsi.brightRed("Не удалось установить Fabric"));
- return false;
- }
- } else if ("neoforge".equalsIgnoreCase(manifest.getLoaderType())) {
- boolean success = lib.installNeoForge(manifest.getMinecraftVersion(), manifest.getLoaderVersion());
- if (!success) {
- System.err.println(ZAnsi.brightRed("Не удалось установить NeoForge"));
- return false;
- }
- } else if ("forge".equalsIgnoreCase(manifest.getLoaderType())) {
- boolean success = lib.installForge(manifest.getMinecraftVersion(), manifest.getLoaderVersion());
- if (!success) {
- System.err.println(ZAnsi.brightRed("Не удалось установить Forge"));
- return false;
- }
- } else {
- boolean success = lib.installMinecraft(manifest.getMinecraftVersion());
- if (!success) {
- System.err.println(ZAnsi.brightRed("Не удалось установить Vanilla Minecraft"));
- return false;
- }
- }
- } else {
- System.out.println(ZAnsi.green("Minecraft уже установлен, пропускаем..."));
- }
-
- // 3. Сканируем локальные файлы ТОЛЬКО если есть файлы для скачивания
- Map localFiles = scanLocalFiles();
-
- // Если в сборке нет файлов (только vanilla/loader), пропускаем diff
- if (manifest.files == null || manifest.files.isEmpty()) {
- System.out.println(ZAnsi.green("Сборка не содержит дополнительных файлов"));
-
- // Обновляем метаданные инстанса
- instance.setServerPack(true);
- instance.setServerPackName(packName);
- instance.setServerVersion(manifest.getVersion());
- instance.setMinecraftVersion(manifest.getMinecraftVersion());
- instance.setLoaderType(manifest.getLoaderType());
- instance.setLoaderVersion(manifest.getLoaderVersion());
- instance.setAssetIndex(manifest.getAssetIndex());
-
- System.out.println(ZAnsi.brightGreen("Сборка успешно установлена!"));
- return true;
- }
-
- // 4. Отправляем diff запрос
- System.out.println(ZAnsi.cyan("Проверка файлов сборки..."));
- DiffResponse diff = getDiff(packName, localFiles);
-
- // 5. Применяем изменения
- boolean success = applyDiff(diff, packName);
-
- if (success) {
- // 6. Обновляем метаданные инстанса
- instance.setServerPack(true);
- instance.setServerPackName(packName);
- instance.setServerVersion(manifest.getVersion());
- instance.setMinecraftVersion(manifest.getMinecraftVersion());
- instance.setLoaderType(manifest.getLoaderType());
- instance.setLoaderVersion(manifest.getLoaderVersion());
- instance.setAssetIndex(manifest.getAssetIndex());
-
- System.out.println(ZAnsi.brightGreen("Сборка успешно установлена!"));
- }
-
- return success;
- }
-
- /**
- * Проверить наличие обновлений для серверной сборки
- */
- public boolean checkForUpdates(String packName) throws Exception {
- if (!instance.isServerPack()) return false;
-
- PackManifest manifest = getPackManifest(packName);
- int serverVersion = manifest.getVersion();
- int localVersion = instance.getServerVersion();
-
- return serverVersion > localVersion;
- }
-
- /**
- * Обновить существующую серверную сборку
- */
- public boolean updatePack(String packName) throws Exception {
- System.out.println(ZAnsi.cyan("Проверка обновлений для " + instance.getName() + "..."));
-
- PackManifest manifest = getPackManifest(packName);
- int serverVersion = manifest.getVersion();
-
- if (serverVersion <= instance.getServerVersion()) {
- System.out.println(ZAnsi.green("Сборка уже актуальна (v" + instance.getServerVersion() + ")"));
- return true;
- }
-
- System.out.println(ZAnsi.yellow("Доступно обновление: v" + instance.getServerVersion() + " → v" + serverVersion));
-
- // Сканируем локальные файлы
- Map localFiles = scanLocalFiles();
-
- // Получаем diff
- DiffResponse diff = getDiff(packName, localFiles);
-
- // Применяем изменения
- boolean success = applyDiff(diff, packName);
-
- if (success) {
- instance.setServerVersion(serverVersion);
- System.out.println(ZAnsi.brightGreen("Сборка обновлена до v" + serverVersion));
- }
-
- return success;
- }
-
- /**
- * Сканирование локальных файлов и вычисление хешей
- */
- private Map scanLocalFiles() throws IOException {
- Map files = new HashMap<>();
- Path instancePath = instance.getPath();
-
- // Игнорируемые директории
- Set ignoredDirs = Set.of(
- "resourcepacks", "shaderpacks", "saves", "logs",
- "crash-reports", "screenshots", "journeymap", "config",
- "natives", "assets", "libraries", "versions", "cache"
- );
-
- if (!Files.exists(instancePath)) {
- return files;
- }
-
- Files.walk(instancePath)
- .filter(Files::isRegularFile)
- .forEach(file -> {
- Path relative = instancePath.relativize(file);
- String path = relative.toString().replace("\\", "/");
-
- // Проверяем, не в игнорируемой ли директории
- for (String ignored : ignoredDirs) {
- if (path.startsWith(ignored + "/") || path.startsWith(ignored + "\\")) {
- return;
- }
- }
-
- try {
- String hash = calculateHash(file);
- files.put(path, hash);
- } catch (Exception e) {
- // Пропускаем файлы, которые не можем прочитать
- }
- });
-
- return files;
- }
-
- /**
- * Отправить diff запрос на сервер
- */
- private DiffResponse getDiff(String packName, Map localFiles) throws Exception {
- String json = gson.toJson(localFiles);
-
- // Получаем токен авторизации
- String accessToken = AuthManager.getAccessToken();
- if (accessToken == null) {
- throw new IOException("Не авторизован. Требуется проходка для скачивания сборок.");
- }
- if (!AuthManager.canDownloadPacks()) {
- throw new IOException("Для скачивания сборок требуется активная проходка");
- }
-
- String url = ZHttpClient.getBaseUrl() + "/pack/" + packName + "/diff";
-
- // Используем HttpURLConnection для полного контроля
- java.net.HttpURLConnection connection = null;
- try {
- java.net.URL urlObj = new java.net.URL(url);
- connection = (java.net.HttpURLConnection) urlObj.openConnection();
- connection.setRequestMethod("POST");
- connection.setRequestProperty("Content-Type", "application/json");
- connection.setRequestProperty("Accept", "application/json");
- connection.setRequestProperty("Authorization", "Bearer " + accessToken);
- connection.setRequestProperty("Content-Length", String.valueOf(json.getBytes("UTF-8").length));
- connection.setDoOutput(true);
- connection.setConnectTimeout(30000);
- connection.setReadTimeout(30000);
-
- // Отправляем JSON
- try (java.io.OutputStream os = connection.getOutputStream()) {
- byte[] input = json.getBytes("UTF-8");
- os.write(input, 0, input.length);
- os.flush();
- }
-
- int responseCode = connection.getResponseCode();
-
- // Читаем ответ
- StringBuilder response = new StringBuilder();
- 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"))) {
- String line;
- while ((line = reader.readLine()) != null) {
- response.append(line);
- }
- }
-
- String responseBody = response.toString();
-
- if (responseCode == 403) {
- throw new IOException("Для скачивания сборок требуется активная проходка. Обратитесь к администратору.");
- }
-
- if (responseCode != 200) {
- throw new IOException("HTTP " + responseCode + ": " + extractErrorFromResponse(responseBody));
- }
-
- return gson.fromJson(responseBody, DiffResponse.class);
-
- } finally {
- if (connection != null) {
- connection.disconnect();
- }
- }
- }
-
- private String extractErrorFromResponse(String body) {
- try {
- JsonObject json = JsonParser.parseString(body).getAsJsonObject();
- if (json.has("detail")) {
- return json.get("detail").getAsString();
- }
- } catch (Exception ignored) {}
- return body.length() > 200 ? body.substring(0, 200) + "..." : body;
- }
-
- /**
- * Применить diff (скачать новые файлы, удалить старые)
- */
- private boolean applyDiff(DiffResponse diff, String packName) {
- System.out.println(ZAnsi.cyan("\nПрименение изменений:"));
- System.out.println(" Загрузить: " + diff.getToDownload().size() + " файлов");
- System.out.println(" Удалить: " + diff.getToDelete().size() + " файлов");
-
- // Создаем директории если нужно
- try {
- Files.createDirectories(instance.getPath());
- } catch (IOException e) {
- System.err.println(ZAnsi.red("Ошибка создания директорий: " + e.getMessage()));
- return false;
- }
-
- // Удаляем файлы
- for (String filePath : diff.getToDelete()) {
- Path fullPath = instance.getPath().resolve(filePath);
- try {
- if (Files.deleteIfExists(fullPath)) {
- System.out.println(ZAnsi.yellow(" Удален: " + filePath));
- }
- } catch (IOException e) {
- System.err.println(ZAnsi.red(" Ошибка удаления " + filePath + ": " + e.getMessage()));
- }
- }
-
- // Скачиваем файлы
- AtomicInteger downloaded = new AtomicInteger(0);
- int total = diff.getToDownload().size();
-
- for (FileInfo file : diff.getToDownload()) {
- String path = file.getPath();
- Path fullPath = instance.getPath().resolve(path);
-
- try {
- // Создаем директории
- Files.createDirectories(fullPath.getParent());
-
- // Скачиваем файл
- downloadFile(file, fullPath);
-
- // Проверяем хеш
- String actualHash = calculateHash(fullPath);
- if (!actualHash.equals(file.getHash())) {
- throw new IOException("Хеш не совпадает! Ожидался: " + file.getHash() +
- ", получен: " + actualHash);
- }
-
- downloaded.incrementAndGet();
- if (total > 0) {
- ProgressBar.show("Скачивание", downloaded.get(), total, "файлов");
- }
-
- } catch (Exception e) {
- System.err.println("\n" + ZAnsi.red(" Ошибка скачивания " + path + ": " + e.getMessage()));
- return false;
- }
- }
-
- if (total > 0) {
- ProgressBar.finish("Скачивание");
- }
-
- return true;
- }
-
- /**
- * Скачать один файл с сервера
- */
- private void downloadFile(FileInfo file, Path destination) throws Exception {
- String url = ZHttpClient.getBaseUrl() + file.getUrl();
-
- HttpRequest request = HttpRequest.newBuilder()
- .uri(java.net.URI.create(url))
- .GET()
- .build();
-
- HttpResponse response = httpClient.send(request,
- HttpResponse.BodyHandlers.ofInputStream());
-
- if (response.statusCode() != 200) {
- throw new IOException("HTTP " + response.statusCode());
- }
-
- // Скачиваем с прогрессом
- try (InputStream in = response.body();
- FileOutputStream out = new FileOutputStream(destination.toFile())) {
-
- byte[] buffer = new byte[8192];
- int bytesRead;
- long totalRead = 0;
- long fileSize = file.getSize();
-
- while ((bytesRead = in.read(buffer)) != -1) {
- out.write(buffer, 0, bytesRead);
- totalRead += bytesRead;
-
- if (fileSize > 0 && totalRead % 8192 == 0) {
- ProgressBar.showDownload(" " + file.getPath(), totalRead, fileSize);
- }
- }
- }
-
- ProgressBar.clearLine();
- }
-
- /**
- * Вычисление SHA256 хеша файла
- */
- private String calculateHash(Path file) throws Exception {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
-
- try (InputStream in = Files.newInputStream(file)) {
- byte[] buffer = new byte[8192];
- int bytesRead;
- while ((bytesRead = in.read(buffer)) != -1) {
- digest.update(buffer, 0, bytesRead);
- }
- }
-
- byte[] hashBytes = digest.digest();
- StringBuilder sb = new StringBuilder();
- for (byte b : hashBytes) {
- sb.append(String.format("%02x", b));
- }
- return sb.toString();
- }
-
- // ====================== Вложенные классы ======================
-
- public static class PackManifest {
- private String pack_name;
- private int version;
- private String minecraft_version;
- private String loader_type;
- private String loader_version;
- private String asset_index;
- private Map files;
-
- public String getPackName() { return pack_name; }
- public int getVersion() { return version; }
- public String getMinecraftVersion() { return minecraft_version; }
- public String getLoaderType() { return loader_type; }
- public String getLoaderVersion() { return loader_version; }
- public String getAssetIndex() { return asset_index != null ? asset_index : minecraft_version; }
- public Map getFiles() { return files; }
- public boolean isEmpty() { return files == null || files.isEmpty(); }
- }
-
- public static class DiffResponse {
- private int version;
- private List to_download;
- private List to_delete;
- private List to_update;
-
- public int getVersion() { return version; }
- public List getToDownload() { return to_download != null ? to_download : new ArrayList<>(); }
- public List getToDelete() { return to_delete != null ? to_delete : new ArrayList<>(); }
- public List getToUpdate() { return to_update != null ? to_update : new ArrayList<>(); }
- }
-
- public static class FileInfo {
- private String path;
- private String url;
- private long size;
- private String hash;
-
- public String getPath() { return path; }
- public String getUrl() { return url; }
- public long getSize() { return size; }
- public String getHash() { return hash; }
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/ServerPack.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/ServerPack.java
deleted file mode 100644
index 9dfd9ee..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/ServerPack.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package me.sashegdev.zernmc.launcher.minecraft;
-
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-
-public class ServerPack {
- private final String name;
- private final int version;
- private final String minecraftVersion;
- private final String loaderType;
- private final String loaderVersion;
- private final LocalDateTime updatedAt;
- private final int filesCount;
-
- public ServerPack(String name, int version, String minecraftVersion,
- String loaderType, String loaderVersion,
- LocalDateTime updatedAt, int filesCount) {
- this.name = name;
- this.version = version;
- this.minecraftVersion = minecraftVersion;
- this.loaderType = loaderType;
- this.loaderVersion = loaderVersion;
- this.updatedAt = updatedAt;
- this.filesCount = filesCount;
- }
-
- public String getName() { return name; }
- public int getVersion() { return version; }
- public String getMinecraftVersion() { return minecraftVersion; }
- public String getLoaderType() { return loaderType; }
- public String getLoaderVersion() { return loaderVersion; }
- public LocalDateTime getUpdatedAt() { return updatedAt; }
- public int getFilesCount() { return filesCount; }
-
- @Override
- public String toString() {
- if (updatedAt != null) {
- return String.format("%s [%s + %s v%d] - %d файлов (обновлен: %s)",
- name, minecraftVersion, loaderType, version, filesCount,
- updatedAt.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")));
- } else {
- return String.format("%s [%s + %s v%d] - %d файлов",
- name, minecraftVersion, loaderType, version, filesCount);
- }
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/FabricInstaller.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/FabricInstaller.java
deleted file mode 100644
index ebfb896..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/FabricInstaller.java
+++ /dev/null
@@ -1,213 +0,0 @@
-package me.sashegdev.zernmc.launcher.minecraft.installer;
-
-import me.sashegdev.zernmc.launcher.minecraft.Instance;
-import me.sashegdev.zernmc.launcher.utils.ProgressBar;
-import me.sashegdev.zernmc.launcher.utils.ZAnsi;
-import me.sashegdev.zernmc.launcher.utils.ZHttpClient;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.time.Duration;
-
-public class FabricInstaller {
-
- private final Instance instance;
- private final HttpClient httpClient = HttpClient.newBuilder()
- .connectTimeout(Duration.ofSeconds(15))
- .build();
-
- public FabricInstaller(Instance instance) {
- this.instance = instance;
- }
-
- public boolean install(String minecraftVersion, String loaderVersion) throws Exception {
- System.out.println(ZAnsi.cyan("Установка Fabric " + loaderVersion + " для Minecraft " + minecraftVersion));
-
- Path instancePath = instance.getPath();
- cleanOldFabricLoaders();
-
- VersionInstaller versionInstaller = new VersionInstaller(instancePath);
- String assetIndex = versionInstaller.install(minecraftVersion);
-
- System.out.println(ZAnsi.green("Asset index получен: " + assetIndex));
-
- instance.setAssetIndex(assetIndex);
- instance.setMinecraftVersion(minecraftVersion);
-
- String installerVersion = getLatestInstallerVersion();
- String installerUrl = "https://maven.fabricmc.net/net/fabricmc/fabric-installer/"
- + installerVersion + "/fabric-installer-" + installerVersion + ".jar";
-
- Path installerJar = instancePath.resolve("fabric-installer.jar");
-
- if (!Files.exists(installerJar)) {
- ProgressBar.show("Скачивание Fabric Installer", 0, 100, "%");
- downloadFileWithFallback(installerUrl, installerJar);
- ProgressBar.finish("Fabric Installer скачан");
- }
-
- System.out.println(ZAnsi.cyan("Запуск Fabric Installer..."));
-
- String fabricVersionId = "fabric-loader-" + loaderVersion + "-" + minecraftVersion;
-
- ProcessBuilder pb = new ProcessBuilder(
- "java", "-jar", installerJar.toAbsolutePath().toString(),
- "client",
- "-dir", instancePath.toAbsolutePath().toString(),
- "-mcversion", minecraftVersion,
- "-loader", loaderVersion,
- "-noprofile"
- );
-
- pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
- pb.redirectError(ProcessBuilder.Redirect.INHERIT);
-
- Process process = pb.start();
- int exitCode = process.waitFor();
-
- if (exitCode != 0) {
- System.out.println(ZAnsi.brightRed("Fabric Installer завершился с ошибкой (код " + exitCode + ")"));
- return false;
- }
-
- Path fabricVersionDir = instancePath.resolve("versions").resolve(fabricVersionId);
-
- if (Files.exists(fabricVersionDir)) {
- System.out.println(ZAnsi.brightGreen("Fabric успешно установлен!"));
-
- instance.setLoaderType("fabric");
- instance.setLoaderVersion(loaderVersion);
- instance.setFabricVersionId(fabricVersionId); // ← СОХРАНЯЕМ
-
- ensureAssetIndexInFabricVersion(fabricVersionDir, assetIndex);
-
- return true;
- } else {
- System.out.println(ZAnsi.brightRed("Fabric Installer отработал, но версия не найдена."));
- return false;
- }
- }
-
- private void downloadFileWithFallback(String url, Path target) throws Exception {
- try {
- ZHttpClient.downloadFile(url, target);
- } catch (Exception e) {
- System.out.println(ZAnsi.yellow("Не удалось скачать Fabric Installer: " + e.getMessage()));
- throw e;
- }
- }
-
- private void ensureAssetIndexInFabricVersion(Path fabricVersionDir, String assetIndex) throws IOException {
- Path versionJson = fabricVersionDir.resolve(fabricVersionDir.getFileName() + ".json");
-
- if (!Files.exists(versionJson)) {
- System.out.println(ZAnsi.yellow("JSON файл версии не найден: " + versionJson));
- return;
- }
-
- String content = Files.readString(versionJson);
-
- // Проверяем и исправляем asset index
- if (!content.contains("\"assets\":\"" + assetIndex + "\"")) {
- System.out.println(ZAnsi.yellow("Исправляем asset index в JSON файле версии..."));
-
- // Заменяем assets на правильное значение
- content = content.replaceAll("\"assets\":\\s*\"[^\"]*\"", "\"assets\": \"" + assetIndex + "\"");
-
- // Также проверяем assetIndex
- if (content.contains("\"assetIndex\"")) {
- content = content.replaceAll("\"assetIndex\":\\s*\"[^\"]*\"", "\"assetIndex\": \"" + assetIndex + "\"");
- }
-
- Files.writeString(versionJson, content);
- System.out.println(ZAnsi.green("Asset index исправлен на: " + assetIndex));
- } else {
- System.out.println(ZAnsi.green("Asset index в JSON версии правильный: " + assetIndex));
- }
- }
-
- private void cleanOldFabricLoaders() throws IOException {
- Path librariesDir = instance.getPath().resolve("libraries/net/fabricmc/fabric-loader");
- if (!Files.exists(librariesDir)) return;
-
- System.out.println(ZAnsi.yellow("Очистка старых версий Fabric Loader..."));
-
- try (var stream = Files.walk(librariesDir)) {
- stream.filter(Files::isDirectory)
- .filter(dir -> dir.getFileName().toString().matches("\\d+\\.\\d+\\.\\d+.*"))
- .forEach(dir -> {
- try {
- Files.walk(dir)
- .sorted((a,b) -> b.compareTo(a))
- .forEach(p -> {
- try { Files.deleteIfExists(p); }
- catch (IOException ignored) {}
- });
- } catch (IOException ignored) {}
- });
- }
- }
-
- private String getLatestInstallerVersion() throws Exception {
- try {
- // Используем ZHttpClient с умным прокси
- String xml = ZHttpClient.downloadString("https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml");
- int start = xml.indexOf("") + 8;
- int end = xml.indexOf("", start);
- return xml.substring(start, end).trim();
- } catch (Exception e) {
- System.out.println(ZAnsi.yellow("Ошибка получения версии Fabric Installer: " + e.getMessage()));
- throw new Exception("Не удалось получить версию Fabric Installer", e);
- }
- }
-
- // под рефактор оставить
- private String downloadString(String url) throws Exception {
- Exception lastException = null;
-
- for (int attempt = 1; attempt <= 3; attempt++) {
- try {
- HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create(url))
- .timeout(Duration.ofSeconds(30 * attempt))
- .header("User-Agent", "ZernMC-Launcher/1.0")
- .GET()
- .build();
-
- HttpResponse resp = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
- if (resp.statusCode() == 200) {
- return resp.body();
- }
- throw new IOException("HTTP " + resp.statusCode());
- } catch (Exception e) {
- lastException = e;
- System.out.println(ZAnsi.yellow("Попытка " + attempt + " не удалась: " + e.getMessage()));
- if (attempt < 3) {
- Thread.sleep(1000 * attempt);
- }
- }
- }
-
- throw lastException;
- }
-
- private void downloadFile(String url, Path target) throws Exception {
- HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create(url))
- .timeout(Duration.ofSeconds(60))
- .GET()
- .build();
-
- HttpResponse response = httpClient.send(request,
- HttpResponse.BodyHandlers.ofFile(target));
-
- if (response.statusCode() != 200) {
- throw new IOException("HTTP " + response.statusCode() + " при скачивании " + url);
- }
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java
deleted file mode 100644
index 2330fd6..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java
+++ /dev/null
@@ -1,270 +0,0 @@
-package me.sashegdev.zernmc.launcher.minecraft.installer;
-
-import me.sashegdev.zernmc.launcher.minecraft.Instance;
-import me.sashegdev.zernmc.launcher.utils.ProgressBar;
-import me.sashegdev.zernmc.launcher.utils.ZAnsi;
-import java.io.*;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.HashMap;
-import java.util.Map;
-
-public class ForgeInstaller {
-
- private final Instance instance;
- private final HttpClient httpClient = HttpClient.newBuilder()
- .connectTimeout(java.time.Duration.ofSeconds(30))
- .build();
-
- public ForgeInstaller(Instance instance) {
- this.instance = instance;
- }
-
- public boolean install(String mcVersion, String forgeVersion) throws Exception {
- System.out.println(ZAnsi.cyan("Установка Forge " + forgeVersion + " для Minecraft " + mcVersion));
-
- // Шаг 1: Устанавливаем vanilla и получаем настоящий assetIndex
- System.out.println(ZAnsi.cyan("Установка базовой версии Minecraft " + mcVersion + "..."));
- VersionInstaller vanillaInstaller = new VersionInstaller(instance.getPath());
-
- String assetIndex = vanillaInstaller.install(mcVersion);
-
- if (assetIndex == null || assetIndex.isEmpty()) {
- System.out.println(ZAnsi.brightRed("Не удалось установить базовую версию Minecraft"));
- return false;
- }
-
- instance.setAssetIndex(assetIndex);
-
- // Шаг 2: Создаём launcher_profiles.json
- createLauncherProfile();
-
- // Шаг 3: Скачиваем Forge Installer с прогресс-баром
- String installerUrl = "https://maven.minecraftforge.net/net/minecraftforge/forge/"
- + mcVersion + "-" + forgeVersion
- + "/forge-" + mcVersion + "-" + forgeVersion + "-installer.jar";
-
- Path installerJar = instance.getPath().resolve("forge-installer.jar");
-
- System.out.println(ZAnsi.cyan("Скачивание Forge Installer..."));
- downloadFileWithProgress(installerUrl, installerJar);
-
- // Шаг 4: Запускаем Forge Installer и показываем его вывод
- System.out.println(ZAnsi.cyan("Запуск Forge Installer..."));
- System.out.println(ZAnsi.yellow("Это может занять несколько минут. Пожалуйста, подождите...\n"));
-
- boolean success = runForgeInstaller(installerJar);
-
- // После успешной установки Forge, но перед сохранением метаданных
- if (success) {
- // Докачиваем пропущенные библиотеки
- try {
- downloadMissingLibraries(mcVersion, forgeVersion);
- } catch (Exception e) {
- System.out.println(ZAnsi.yellow("Предупреждение: не удалось докачать некоторые библиотеки: " + e.getMessage()));
- }
-
- System.out.println(ZAnsi.brightGreen("\nForge " + forgeVersion + " успешно установлен!"));
- instance.setMinecraftVersion(mcVersion);
- instance.setLoaderType("forge");
- instance.setLoaderVersion(forgeVersion);
-
- // Очищаем временный файл установщика
- Files.deleteIfExists(installerJar);
- return true;
- } else {
- System.out.println(ZAnsi.brightRed("\nОшибка при установке Forge!"));
- return false;
- }
- }
-
- private void createLauncherProfile() throws IOException {
- Path profilePath = instance.getPath().resolve("launcher_profiles.json");
- if (Files.exists(profilePath)) return;
-
- String minimalProfile = """
- {
- "profiles": {},
- "selectedProfile": "Default"
- }
- """;
- Files.writeString(profilePath, minimalProfile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
- System.out.println(ZAnsi.yellow("Создан launcher_profiles.json"));
- }
-
- private void downloadFileWithProgress(String url, Path target) throws Exception {
- HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create(url))
- .GET()
- .build();
-
- HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
-
- if (response.statusCode() != 200) {
- throw new IOException("HTTP " + response.statusCode());
- }
-
- long contentLength = response.headers().firstValueAsLong("Content-Length").orElse(-1);
-
- try (InputStream in = response.body();
- FileOutputStream out = new FileOutputStream(target.toFile())) {
-
- byte[] buffer = new byte[8192];
- int bytesRead;
- long totalRead = 0;
- int lastPercent = -1;
-
- while ((bytesRead = in.read(buffer)) != -1) {
- out.write(buffer, 0, bytesRead);
- totalRead += bytesRead;
-
- if (contentLength > 0) {
- int percent = (int) ((totalRead * 100) / contentLength);
- if (percent != lastPercent) {
- String downloaded = ProgressBar.formatBytes(totalRead);
- String total = ProgressBar.formatBytes(contentLength);
- ProgressBar.show("Forge Installer", percent, 100, "% (" + downloaded + "/" + total + ")");
- lastPercent = percent;
- }
- } else {
- // Если размер неизвестен, показываем анимацию
- char[] spinner = {'|', '/', '-', '\\'};
- int idx = (int) (totalRead / 1024) % 4;
- System.out.print("\rСкачивание Forge Installer: " + ProgressBar.formatBytes(totalRead) + " " + spinner[idx]);
- }
- }
- }
-
- ProgressBar.finish("Forge Installer (" + ProgressBar.formatBytes(Files.size(target)) + ")");
- }
-
- private boolean runForgeInstaller(Path installerJar) throws IOException, InterruptedException {
- // Пробуем до 3 раз с разными опциями
- int maxRetries = 3;
- int attempt = 1;
-
- while (attempt <= maxRetries) {
- System.out.println(ZAnsi.cyan("Попытка " + attempt + " из " + maxRetries));
-
- ProcessBuilder pb = new ProcessBuilder(
- "java",
- "-jar",
- installerJar.toAbsolutePath().toString(),
- "--installClient"
- );
-
- // Добавляем JVM аргументы для увеличения таймаутов
- pb.environment().put("JAVA_OPTS", "-Dhttp.connectionTimeout=60000 -Dhttp.socketTimeout=60000");
-
- pb.directory(instance.getPath().toFile());
- pb.redirectErrorStream(true);
-
- Process process = pb.start();
-
- // Читаем вывод в реальном времени
- StringBuilder output = new StringBuilder();
- boolean hasErrors = false;
-
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
- String line;
- while ((line = reader.readLine()) != null) {
- output.append(line).append("\n");
-
- // Форматируем вывод Forge Installer
- if (line.contains("Downloading") || line.contains("Extracting")) {
- System.out.println(ZAnsi.blue(" -> " + line));
- } else if (line.contains("SUCCESS") || line.contains("successfully")) {
- System.out.println(ZAnsi.brightGreen(" + " + line));
- } else if (line.contains("WARNING") || line.contains("warning")) {
- System.out.println(ZAnsi.yellow(" ! " + line));
- } else if (line.contains("ERROR") || line.contains("error") || line.contains("failed") || line.contains("timed out")) {
- System.out.println(ZAnsi.brightRed(" X " + line));
- if (line.contains("timed out") || line.contains("failed to download")) {
- hasErrors = true;
- }
- } else if (!line.isBlank()) {
- System.out.println(" " + line);
- }
- }
- }
-
- int exitCode = process.waitFor();
-
- // Если успешно или нет ошибок скачивания
- if (exitCode == 0 && !hasErrors) {
- return true;
- }
-
- // Если ошибка и это не последняя попытка
- if (attempt < maxRetries) {
- System.out.println(ZAnsi.yellow("Ошибка при установке. Повторная попытка через 5 секунд..."));
- Thread.sleep(5000);
-
- // Очищаем временные файлы перед повтором
- Path librariesDir = instance.getPath().resolve("libraries");
- if (Files.exists(librariesDir)) {
- // Удаляем только частично скачанные библиотеки Forge
- try (var stream = Files.walk(librariesDir)) {
- stream.filter(p -> p.toString().contains("asm") && p.toString().endsWith(".jar"))
- .forEach(p -> {
- try { Files.deleteIfExists(p); }
- catch (IOException e) { /* ignore */ }
- });
- }
- }
- } else {
- System.out.println(ZAnsi.brightRed("Forge Installer завершился с кодом ошибки: " + exitCode));
-
- // Показываем возможное решение
- if (output.toString().contains("timed out")) {
- System.out.println(ZAnsi.yellow("\nВозможные решения:"));
- System.out.println(ZAnsi.yellow("1. Проверьте интернет-соединение"));
- System.out.println(ZAnsi.yellow("2. Запустите лаунчер от имени администратора"));
- System.out.println(ZAnsi.yellow("3. Временно отключите антивирус/брандмауэр"));
- System.out.println(ZAnsi.yellow("4. Попробуйте установить другую версию Forge"));
- }
- }
-
- attempt++;
- }
-
- return false;
- }
-
- private void downloadMissingLibraries(String mcVersion, String forgeVersion) throws Exception {
- System.out.println(ZAnsi.cyan("Проверка и докачка отсутствующих библиотек..."));
-
- // Список проблемных библиотек и их альтернативные URL
- Map alternativeUrls = new HashMap<>();
- alternativeUrls.put("org/ow2/asm/asm/9.6/asm-9.6.jar",
- "https://repo1.maven.org/maven2/org/ow2/asm/asm/9.6/asm-9.6.jar");
- alternativeUrls.put("org/ow2/asm/asm/9.6/asm-9.6.jar",
- "https://mirrors.huaweicloud.com/repository/maven/org/ow2/asm/asm/9.6/asm-9.6.jar");
-
- Path librariesDir = instance.getPath().resolve("libraries");
-
- for (Map.Entry entry : alternativeUrls.entrySet()) {
- Path target = librariesDir.resolve(entry.getKey());
- if (!Files.exists(target)) {
- Files.createDirectories(target.getParent());
- System.out.println(ZAnsi.yellow("Докачка: " + target.getFileName()));
-
- for (int attempt = 1; attempt <= 3; attempt++) {
- try {
- downloadFileWithProgress(entry.getValue(), target);
- break;
- } catch (Exception e) {
- if (attempt == 3) throw e;
- System.out.println(ZAnsi.yellow("Повторная попытка " + attempt + "/3..."));
- Thread.sleep(2000);
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/NeoForgeInstaller.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/NeoForgeInstaller.java
deleted file mode 100644
index 5f345d0..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/NeoForgeInstaller.java
+++ /dev/null
@@ -1,271 +0,0 @@
-package me.sashegdev.zernmc.launcher.minecraft.installer;
-
-import me.sashegdev.zernmc.launcher.minecraft.Instance;
-import me.sashegdev.zernmc.launcher.utils.ProgressBar;
-import me.sashegdev.zernmc.launcher.utils.ZAnsi;
-
-import java.io.*;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.HashMap;
-import java.util.Map;
-
-public class NeoForgeInstaller {
-
- private final Instance instance;
- private final HttpClient httpClient = HttpClient.newBuilder()
- .connectTimeout(java.time.Duration.ofSeconds(30))
- .build();
-
- public NeoForgeInstaller(Instance instance) {
- this.instance = instance;
- }
-
- public boolean install(String mcVersion, String neoForgeVersion) throws Exception {
- System.out.println(ZAnsi.cyan("Установка NeoForge " + neoForgeVersion + " для Minecraft " + mcVersion));
-
- System.out.println(ZAnsi.cyan("Установка базовой версии Minecraft " + mcVersion + "..."));
- VersionInstaller vanillaInstaller = new VersionInstaller(instance.getPath());
- String assetIndex = vanillaInstaller.install(mcVersion);
-
- if (assetIndex == null || assetIndex.isEmpty()) {
- System.out.println(ZAnsi.brightRed("Не удалось установить базовую версию Minecraft"));
- return false;
- }
-
- instance.setAssetIndex(assetIndex);
- createLauncherProfile();
-
- String mavenGroup = getMavenGroup(mcVersion);
- String mavenArtifact = getMavenArtifact(mcVersion);
-
- String installerUrl = "https://maven.neoforged.net/releases/"
- + mavenGroup.replace('.', '/') + "/"
- + mavenArtifact + "/"
- + neoForgeVersion
- + "/" + mavenArtifact + "-" + neoForgeVersion + "-installer.jar";
-
- Path installerJar = instance.getPath().resolve("neoforge-installer.jar");
-
- System.out.println(ZAnsi.cyan("Скачивание NeoForge Installer..."));
- downloadFileWithProgress(installerUrl, installerJar);
-
- System.out.println(ZAnsi.cyan("Запуск NeoForge Installer..."));
- System.out.println(ZAnsi.yellow("Это может занять несколько минут. Пожалуйста, подождите...\n"));
-
- boolean success = runNeoForgeInstaller(installerJar);
-
- if (success) {
- try {
- downloadMissingLibraries(mcVersion, neoForgeVersion, mavenGroup, mavenArtifact);
- } catch (Exception e) {
- System.out.println(ZAnsi.yellow("Предупреждение: не удалось докачать некоторые библиотеки: " + e.getMessage()));
- }
-
- System.out.println(ZAnsi.brightGreen("\nNeoForge " + neoForgeVersion + " успешно установлен!"));
- instance.setMinecraftVersion(mcVersion);
- instance.setLoaderType("neoforge");
- instance.setLoaderVersion(neoForgeVersion);
-
- Files.deleteIfExists(installerJar);
- return true;
- } else {
- System.out.println(ZAnsi.brightRed("\nОшибка при установке NeoForge!"));
- return false;
- }
- }
-
- private String getMavenGroup(String mcVersion) {
- if (mcVersion.equals("1.20.1")) {
- return "net.neoforged";
- }
- return "net.neoforged";
- }
-
- private String getMavenArtifact(String mcVersion) {
- if (mcVersion.equals("1.20.1")) {
- return "forge";
- }
- return "neoforge";
- }
-
- private void createLauncherProfile() throws IOException {
- Path profilePath = instance.getPath().resolve("launcher_profiles.json");
- if (Files.exists(profilePath)) return;
-
- String minimalProfile = """
- {
- "profiles": {},
- "selectedProfile": "Default"
- }
- """;
- Files.writeString(profilePath, minimalProfile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
- System.out.println(ZAnsi.yellow("Создан launcher_profiles.json"));
- }
-
- private void downloadFileWithProgress(String url, Path target) throws Exception {
- HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create(url))
- .GET()
- .build();
-
- HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
-
- if (response.statusCode() != 200) {
- throw new IOException("HTTP " + response.statusCode());
- }
-
- long contentLength = response.headers().firstValueAsLong("Content-Length").orElse(-1);
-
- try (InputStream in = response.body();
- FileOutputStream out = new FileOutputStream(target.toFile())) {
-
- byte[] buffer = new byte[8192];
- int bytesRead;
- long totalRead = 0;
- int lastPercent = -1;
-
- while ((bytesRead = in.read(buffer)) != -1) {
- out.write(buffer, 0, bytesRead);
- totalRead += bytesRead;
-
- if (contentLength > 0) {
- int percent = (int) ((totalRead * 100) / contentLength);
- if (percent != lastPercent) {
- String downloaded = ProgressBar.formatBytes(totalRead);
- String total = ProgressBar.formatBytes(contentLength);
- ProgressBar.show("NeoForge Installer", percent, 100, "% (" + downloaded + "/" + total + ")");
- lastPercent = percent;
- }
- } else {
- char[] spinner = {'|', '/', '-', '\\'};
- int idx = (int) (totalRead / 1024) % 4;
- System.out.print("\rСкачивание NeoForge Installer: " + ProgressBar.formatBytes(totalRead) + " " + spinner[idx]);
- }
- }
- }
-
- ProgressBar.finish("NeoForge Installer (" + ProgressBar.formatBytes(Files.size(target)) + ")");
- }
-
- private boolean runNeoForgeInstaller(Path installerJar) throws IOException, InterruptedException {
- int maxRetries = 3;
- int attempt = 1;
-
- while (attempt <= maxRetries) {
- System.out.println(ZAnsi.cyan("Попытка " + attempt + " из " + maxRetries));
-
- ProcessBuilder pb = new ProcessBuilder(
- "java",
- "-jar",
- installerJar.toAbsolutePath().toString(),
- "--installClient"
- );
-
- pb.environment().put("JAVA_OPTS", "-Dhttp.connectionTimeout=60000 -Dhttp.socketTimeout=60000");
- pb.directory(instance.getPath().toFile());
- pb.redirectErrorStream(true);
-
- Process process = pb.start();
-
- StringBuilder output = new StringBuilder();
- boolean hasErrors = false;
-
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
- String line;
- while ((line = reader.readLine()) != null) {
- output.append(line).append("\n");
-
- if (line.contains("Downloading") || line.contains("Extracting")) {
- System.out.println(ZAnsi.blue(" -> " + line));
- } else if (line.contains("SUCCESS") || line.contains("successfully")) {
- System.out.println(ZAnsi.brightGreen(" + " + line));
- } else if (line.contains("WARNING") || line.contains("warning")) {
- System.out.println(ZAnsi.yellow(" ! " + line));
- } else if (line.contains("ERROR") || line.contains("error") || line.contains("failed") || line.contains("timed out")) {
- System.out.println(ZAnsi.brightRed(" X " + line));
- if (line.contains("timed out") || line.contains("failed to download")) {
- hasErrors = true;
- }
- } else if (!line.isBlank()) {
- System.out.println(" " + line);
- }
- }
- }
-
- int exitCode = process.waitFor();
-
- if (exitCode == 0 && !hasErrors) {
- return true;
- }
-
- if (attempt < maxRetries) {
- System.out.println(ZAnsi.yellow("Ошибка при установке. Повторная попытка через 5 секунд..."));
- Thread.sleep(5000);
-
- Path librariesDir = instance.getPath().resolve("libraries");
- if (Files.exists(librariesDir)) {
- try (var stream = Files.walk(librariesDir)) {
- stream.filter(p -> p.toString().contains("asm") && p.toString().endsWith(".jar"))
- .forEach(p -> {
- try { Files.deleteIfExists(p); }
- catch (IOException e) { /* ignore */ }
- });
- }
- }
- } else {
- System.out.println(ZAnsi.brightRed("NeoForge Installer завершился с кодом ошибки: " + exitCode));
-
- if (output.toString().contains("timed out")) {
- System.out.println(ZAnsi.yellow("\nВозможные решения:"));
- System.out.println(ZAnsi.yellow("1. Проверьте интернет-соединение"));
- System.out.println(ZAnsi.yellow("2. Запустите лаунчер от имени администратора"));
- System.out.println(ZAnsi.yellow("3. Временно отключите антивирус/брандмауэр"));
- System.out.println(ZAnsi.yellow("4. Попробуйте установить другую версию NeoForge"));
- }
- }
-
- attempt++;
- }
-
- return false;
- }
-
- private void downloadMissingLibraries(String mcVersion, String neoForgeVersion, String mavenGroup, String mavenArtifact) throws Exception {
- System.out.println(ZAnsi.cyan("Проверка и докачка отсутствующих библиотек..."));
-
- Map alternativeUrls = new HashMap<>();
- alternativeUrls.put("org/ow2/asm/asm/9.6/asm-9.6.jar",
- "https://repo1.maven.org/maven2/org/ow2/asm/asm/9.6/asm-9.6.jar");
- alternativeUrls.put("org/ow2/asm/asm-commons/9.6/asm-commons-9.6.jar",
- "https://repo1.maven.org/maven2/org/ow2/asm/asm-commons/9.6/asm-commons-9.6.jar");
- alternativeUrls.put("org/ow2/asm/asm-tree/9.6/asm-tree-9.6.jar",
- "https://repo1.maven.org/maven2/org/ow2/asm/asm-tree/9.6/asm-tree-9.6.jar");
-
- Path librariesDir = instance.getPath().resolve("libraries");
-
- for (Map.Entry entry : alternativeUrls.entrySet()) {
- Path target = librariesDir.resolve(entry.getKey());
- if (!Files.exists(target)) {
- Files.createDirectories(target.getParent());
- System.out.println(ZAnsi.yellow("Докачка: " + target.getFileName()));
-
- for (int attempt = 1; attempt <= 3; attempt++) {
- try {
- downloadFileWithProgress(entry.getValue(), target);
- break;
- } catch (Exception e) {
- if (attempt == 3) throw e;
- System.out.println(ZAnsi.yellow("Повторная попытка " + attempt + "/3..."));
- Thread.sleep(2000);
- }
- }
- }
- }
- }
-}
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java
deleted file mode 100644
index 3c76009..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java
+++ /dev/null
@@ -1,245 +0,0 @@
-package me.sashegdev.zernmc.launcher.minecraft.installer;
-
-import me.sashegdev.zernmc.launcher.minecraft.model.MinecraftVersion;
-import me.sashegdev.zernmc.launcher.utils.ProgressBar;
-import me.sashegdev.zernmc.launcher.utils.ZAnsi;
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.time.Duration;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class VersionInstaller {
-
- private final Path minecraftDir;
- private final HttpClient httpClient;
- private final ExecutorService executor = Executors.newFixedThreadPool(32);
-
- public VersionInstaller(Path minecraftDir) {
- this.minecraftDir = minecraftDir;
- this.httpClient = HttpClient.newBuilder()
- .connectTimeout(Duration.ofSeconds(15))
- .build();
- }
-
- public List getAvailableVersions() throws Exception {
- String jsonString = downloadString("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json");
- JSONObject root = new JSONObject(jsonString);
- JSONArray versionsArray = root.getJSONArray("versions");
-
- List versions = new ArrayList<>();
-
- for (int i = 0; i < versionsArray.length(); i++) {
- JSONObject v = versionsArray.getJSONObject(i);
- String id = v.getString("id");
- String type = v.getString("type");
- String releaseTimeStr = v.getString("releaseTime").replace("Z", "").replace("+00:00", "");
- String url = v.getString("url");
-
- LocalDateTime releaseTime = LocalDateTime.parse(releaseTimeStr);
- versions.add(new MinecraftVersion(id, type, releaseTime, url));
- }
-
- versions.sort((a, b) -> b.getReleaseTime().compareTo(a.getReleaseTime()));
- return versions;
- }
-
- public String install(String versionId) throws Exception {
- System.out.println(ZAnsi.cyan("Полная установка Minecraft " + versionId + "..."));
- Path versionDir = minecraftDir.resolve("versions").resolve(versionId);
- Files.createDirectories(versionDir);
-
- String versionUrl = getVersionUrl(versionId);
- if (versionUrl == null) throw new Exception("Версия " + versionId + " не найдена");
-
- String versionJson = downloadString(versionUrl);
- Files.writeString(versionDir.resolve(versionId + ".json"), versionJson);
-
- JSONObject versionData = new JSONObject(versionJson);
-
- // client.jar
- downloadFile(versionData.getJSONObject("downloads").getJSONObject("client").getString("url"),
- versionDir.resolve(versionId + ".jar"), "client.jar");
-
- // Библиотеки
- System.out.println(ZAnsi.cyan("Скачивание библиотек..."));
- downloadLibraries(versionData.getJSONArray("libraries"));
-
- String assetIndex;
- if (versionData.has("assetIndex")) {
- assetIndex = versionData.getJSONObject("assetIndex").getString("id");
- } else {
- assetIndex = versionData.getString("assets");
- }
-
- System.out.println(ZAnsi.cyan("Asset index: " + assetIndex));
-
- // Скачиваем ассеты используя правильный индекс
- System.out.println(ZAnsi.cyan("Скачивание ассетов..."));
- downloadAssets(versionData, assetIndex);
-
- System.out.println(ZAnsi.brightGreen("\nMinecraft " + versionId + " полностью установлен!"));
- return assetIndex; // ← возвращаем "5" а не "1.20.1"
- }
-
- private void downloadLibraries(JSONArray libraries) throws Exception {
- int total = libraries.length();
- int count = 0;
-
- for (int i = 0; i < total; i++) {
- JSONObject lib = libraries.getJSONObject(i);
- if (lib.has("downloads") && lib.getJSONObject("downloads").has("artifact")) {
- JSONObject art = lib.getJSONObject("downloads").getJSONObject("artifact");
- String url = art.getString("url");
- String path = art.getString("path");
-
- Path target = minecraftDir.resolve("libraries").resolve(path);
- Files.createDirectories(target.getParent());
-
- try {
- downloadFile(url, target, "library");
- } catch (Exception e) {
- // Пропускаем проблемные библиотеки
- }
- }
- count++;
- ProgressBar.show("Библиотеки", count, total, "файлов");
- }
- ProgressBar.finish("Библиотеки загружены");
- }
-
- private void downloadAssets(JSONObject versionData, String assetIndex) throws Exception {
- // Находим URL для asset index
- JSONObject assetIndexInfo = versionData.getJSONObject("assetIndex");
- String indexUrl = assetIndexInfo.getString("url");
-
- Path indexesDir = minecraftDir.resolve("assets/indexes");
- Files.createDirectories(indexesDir);
- Path indexPath = indexesDir.resolve(assetIndex + ".json"); // ← используем assetIndex
-
- System.out.println(ZAnsi.cyan("Скачивание asset index (" + assetIndex + ")..."));
- downloadFile(indexUrl, indexPath, "asset index");
-
- String jsonContent = Files.readString(indexPath);
- JSONObject root = new JSONObject(jsonContent);
- JSONObject objects = root.getJSONObject("objects");
-
- System.out.println(ZAnsi.cyan("Скачивание " + objects.length() + " объектов ассетов (index: " + assetIndex + ")..."));
-
- int total = objects.length();
- int[] success = {0};
- int[] failed = {0};
-
- List> futures = new ArrayList<>();
-
- for (String key : objects.keySet()) {
- JSONObject asset = objects.getJSONObject(key);
- String hash = asset.getString("hash"); // ← вот это правильный хеш!
-
- String url = "https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash;
- Path target = minecraftDir.resolve("assets/objects")
- .resolve(hash.substring(0, 2))
- .resolve(hash);
-
- Files.createDirectories(target.getParent());
-
- CompletableFuture future = CompletableFuture.runAsync(() -> {
- boolean downloaded = false;
- for (int attempt = 1; attempt <= 3; attempt++) {
- try {
- downloadFile(url, target, "");
- synchronized (this) {
- success[0]++;
- ProgressBar.show("Ассеты", success[0], total, "файлов");
- }
- downloaded = true;
- break;
- } catch (Exception e) {
- if (attempt == 3) {
- synchronized (this) {
- failed[0]++;
- }
- System.err.println("Не удалось скачать " + hash);
- } else {
- try { Thread.sleep(500 * attempt); } catch (InterruptedException ignored) {}
- }
- }
- }
- }, executor);
-
- futures.add(future);
- }
-
- CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
-
- ProgressBar.finish("Ассеты загружены (" + success[0] + " успешно, " + failed[0] + " пропущено)");
-
- if (failed[0] > 0) {
- System.out.println(ZAnsi.yellow("Предупреждение: " + failed[0] + " файлов ассетов не удалось скачать."));
- System.out.println(ZAnsi.yellow("Игра запустится, но некоторые текстуры/звуки могут отсутствовать."));
- }
- }
-
- public String getAssetIndexId(String versionId) throws Exception {
- String versionUrl = getVersionUrl(versionId);
- if (versionUrl == null) throw new Exception("Версия не найдена");
-
- String versionJson = downloadString(versionUrl);
- JSONObject versionData = new JSONObject(versionJson);
-
- if (versionData.has("assetIndex") && versionData.getJSONObject("assetIndex").has("id")) {
- return versionData.getJSONObject("assetIndex").getString("id"); // "5" для 1.20.1
- }
- return versionData.getString("assets"); // fallback (очень старые версии)
- }
-
- private String getVersionUrl(String versionId) throws Exception {
- for (MinecraftVersion v : getAvailableVersions()) {
- if (v.getId().equals(versionId)) return v.getUrl();
- }
- return null;
- }
-
- private String downloadString(String url) throws Exception {
- HttpRequest req = HttpRequest.newBuilder().uri(URI.create(url)).GET().build();
- HttpResponse resp = httpClient.send(req, HttpResponse.BodyHandlers.ofString());
- if (resp.statusCode() != 200) throw new IOException("HTTP " + resp.statusCode());
- return resp.body();
- }
-
- private void downloadFile(String url, Path target, String label) throws Exception {
- if (!label.isEmpty()) {
- ProgressBar.clearLine();
- System.out.println(ZAnsi.cyan("Скачивание " + label + "..."));
- }
-
- HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create(url))
- .GET()
- .build();
-
- HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofFile(target));
-
- if (response.statusCode() != 200) {
- if (label.isEmpty()) return; // для ассетов молча
- throw new IOException("HTTP " + response.statusCode() + " при скачивании " + label);
- }
-
- if (!label.isEmpty()) {
- long size = Files.size(target);
- ProgressBar.finish(label + " (" + ProgressBar.formatBytes(size) + ")");
- }
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/launch/LaunchCommandBuilder.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/launch/LaunchCommandBuilder.java
deleted file mode 100644
index 0be55be..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/launch/LaunchCommandBuilder.java
+++ /dev/null
@@ -1,426 +0,0 @@
-package me.sashegdev.zernmc.launcher.minecraft.launch;
-
-import me.sashegdev.zernmc.launcher.minecraft.Instance;
-import me.sashegdev.zernmc.launcher.minecraft.model.LaunchOptions;
-import me.sashegdev.zernmc.launcher.utils.ZAnsi;
-import org.json.JSONObject;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class LaunchCommandBuilder {
-
- private final Instance instance;
-
- public LaunchCommandBuilder(Instance instance) {
- this.instance = instance;
- }
-
- public List build(LaunchOptions options) throws Exception {
- System.out.println(ZAnsi.cyan("Генерация команды запуска для " + instance.getName() + "..."));
-
- List command = new ArrayList<>();
-
- String javaPath = "java";
- command.add(javaPath);
-
- command.addAll(getJvmArguments(options));
-
- Path nativesDir = instance.getPath().resolve("natives");
- if (!Files.exists(nativesDir)) {
- Files.createDirectories(nativesDir);
- }
- command.add("-Djava.library.path=" + nativesDir.toAbsolutePath());
-
- VersionManifest manifest = resolveVersionManifest();
- if (manifest != null) {
- command.add("-cp");
- command.add(buildClasspathFromManifest(manifest));
-
- String mainClass = resolveMainClass(manifest);
- command.add(mainClass);
-
- command.addAll(resolveGameArguments(manifest, options));
- } else {
- command.add("-cp");
- command.add(buildVanillaClasspath());
- command.add(getVanillaMainClass());
- command.addAll(getVanillaGameArguments(options));
- }
-
- return command;
- }
-
- private VersionManifest resolveVersionManifest() {
- try {
- Path versionJson = findVersionJson();
- if (versionJson != null && Files.exists(versionJson)) {
- String content = Files.readString(versionJson);
- JSONObject json = new JSONObject(content);
- System.out.println(ZAnsi.green("Найден version.json: " + versionJson.getFileName()));
- return new VersionManifest(json);
- }
- } catch (Exception e) {
- System.out.println(ZAnsi.yellow("Не удалось загрузить version.json: " + e.getMessage()));
- }
- return null;
- }
-
- private Path findVersionJson() {
- Path versionsDir = instance.getPath().resolve("versions");
- String loaderType = instance.getLoaderType().toLowerCase();
- String mcVersion = instance.getMinecraftVersion();
- String loaderVersion = instance.getLoaderVersion();
-
- if ("forge".equals(loaderType) || "neoforge".equals(loaderType)) {
- String[] candidates = {
- getVersionId(),
- mcVersion + "-" + loaderType + "-" + loaderVersion,
- loaderType + "-" + loaderVersion,
- mcVersion + "-" + loaderVersion,
- mcVersion
- };
- for (String candidate : candidates) {
- Path jsonPath = versionsDir.resolve(candidate).resolve(candidate + ".json");
- if (Files.exists(jsonPath)) {
- return jsonPath;
- }
- }
-
- try {
- if (Files.exists(versionsDir)) {
- try (var stream = Files.list(versionsDir)) {
- return stream
- .filter(Files::isDirectory)
- .filter(dir -> dir.getFileName().toString().contains("forge") ||
- dir.getFileName().toString().contains("neoforge"))
- .filter(dir -> dir.getFileName().toString().contains(mcVersion))
- .findFirst()
- .map(dir -> dir.resolve(dir.getFileName().toString() + ".json"))
- .filter(Files::exists)
- .orElse(null);
- }
- }
- } catch (Exception ignored) {}
- }
-
- Path fallback = versionsDir.resolve(mcVersion).resolve(mcVersion + ".json");
- if (Files.exists(fallback)) {
- return fallback;
- }
-
- return null;
- }
-
- private String getVersionId() {
- String loaderType = instance.getLoaderType().toLowerCase();
- String mcVersion = instance.getMinecraftVersion();
- String loaderVer = instance.getLoaderVersion();
-
- if ("vanilla".equals(loaderType)) {
- return mcVersion;
- }
- else if ("fabric".equals(loaderType)) {
- String fabricId = instance.getFabricVersionId();
- if (fabricId != null && !fabricId.isEmpty()) {
- return fabricId;
- }
- return "fabric-loader-" + loaderVer + "-" + mcVersion;
- }
- else if ("forge".equals(loaderType)) {
- return mcVersion + "-forge-" + loaderVer;
- }
- else if ("neoforge".equals(loaderType)) {
- if (mcVersion.equals("1.20.1")) {
- return mcVersion + "-neoforge-" + loaderVer;
- }
- return "neoforge-" + loaderVer;
- }
-
- return mcVersion;
- }
-
- private String resolveMainClass(VersionManifest manifest) {
- return manifest.getMainClass();
- }
-
- private String getVanillaMainClass() {
- String loaderType = instance.getLoaderType().toLowerCase();
- if ("fabric".equals(loaderType)) {
- return "net.fabricmc.loader.impl.launch.knot.KnotClient";
- }
- return "net.minecraft.client.main.Main";
- }
-
- private List resolveGameArguments(VersionManifest manifest, LaunchOptions options) {
- List args = new ArrayList<>();
- Map vars = buildVariableMap(options);
-
- for (String raw : manifest.getGameArguments()) {
- args.add(resolveVariable(raw, vars));
- }
-
- if (options.getWidth() > 0) {
- args.add("--width");
- args.add(String.valueOf(options.getWidth()));
- }
- if (options.getHeight() > 0) {
- args.add("--height");
- args.add(String.valueOf(options.getHeight()));
- }
-
- return args;
- }
-
- private List getVanillaGameArguments(LaunchOptions options) {
- List args = new ArrayList<>();
-
- args.add("--version");
- args.add(instance.getName());
- args.add("--gameDir");
- args.add(instance.getPath().toAbsolutePath().toString());
- args.add("--assetsDir");
- args.add(instance.getPath().resolve("assets").toAbsolutePath().toString());
- args.add("--assetIndex");
- String assetIndex = instance.getAssetIndex();
- if (assetIndex == null || assetIndex.isEmpty()) {
- assetIndex = instance.getMinecraftVersion();
- System.out.println(ZAnsi.yellow("Asset index не найден, использую версию: " + assetIndex));
- } else {
- System.out.println(ZAnsi.green("Использую asset index: " + assetIndex));
- }
- args.add(assetIndex);
- args.add("--username");
- args.add(options.getUsername() != null ? options.getUsername() : "Player");
- args.add("--accessToken");
- args.add(options.getAccessToken() != null ? options.getAccessToken() : "0");
- args.add("--uuid");
- args.add(options.getUuid() != null ? options.getUuid() : "00000000-0000-0000-0000-000000000000");
- args.add("--userType");
- args.add("legacy");
-
- return args;
- }
-
- private Map buildVariableMap(LaunchOptions options) {
- Map vars = new HashMap<>();
-
- Path gameDir = instance.getPath().toAbsolutePath();
- Path assetsDir = gameDir.resolve("assets");
- Path nativesDir = gameDir.resolve("natives");
- Path librariesDir = gameDir.resolve("libraries");
-
- vars.put("version_name", instance.getName());
- vars.put("game_directory", gameDir.toString());
- vars.put("assets_root", assetsDir.toString());
- vars.put("assets_index_name", instance.getAssetIndex() != null ? instance.getAssetIndex() : instance.getMinecraftVersion());
- vars.put("auth_player_name", options.getUsername() != null ? options.getUsername() : "Player");
- vars.put("auth_access_token", options.getAccessToken() != null ? options.getAccessToken() : "0");
- vars.put("auth_uuid", options.getUuid() != null ? options.getUuid() : "00000000-0000-0000-0000-000000000000");
- vars.put("auth_xuid", "0");
- vars.put("user_type", "legacy");
- vars.put("version_type", "release");
- vars.put("natives_directory", nativesDir.toString());
- vars.put("library_directory", librariesDir.toString());
- vars.put("launcher_name", "ZernMC");
- vars.put("launcher_version", "1.0");
- vars.put("classpath_separator", System.getProperty("os.name").toLowerCase().contains("win") ? ";" : ":");
- vars.put("resolution_width", String.valueOf(options.getWidth() > 0 ? options.getWidth() : 1920));
- vars.put("resolution_height", String.valueOf(options.getHeight() > 0 ? options.getHeight() : 1080));
- vars.put("game_directory", gameDir.toString());
-
- String loaderType = instance.getLoaderType().toLowerCase();
- if ("forge".equals(loaderType)) {
- vars.put("forge_version", instance.getLoaderVersion() != null ? instance.getLoaderVersion() : "");
- } else if ("neoforge".equals(loaderType)) {
- vars.put("neoforge_version", instance.getLoaderVersion() != null ? instance.getLoaderVersion() : "");
- vars.put("fml.neoForgeVersion", instance.getLoaderVersion() != null ? instance.getLoaderVersion() : "");
- vars.put("fml.neoForgeGroup", "net.neoforged");
- }
-
- return vars;
- }
-
- private String resolveVariable(String raw, Map vars) {
- if (!raw.contains("${")) return raw;
- String result = raw;
- for (Map.Entry entry : vars.entrySet()) {
- result = result.replace("${" + entry.getKey() + "}", entry.getValue());
- }
- return result;
- }
-
- private String buildClasspathFromManifest(VersionManifest manifest) throws Exception {
- List paths = new ArrayList<>();
- Path librariesDir = instance.getPath().resolve("libraries");
-
- for (VersionManifest.Library lib : manifest.getLibraries()) {
- Path libPath = librariesDir.resolve(lib.artifactPath);
- if (Files.exists(libPath)) {
- paths.add(libPath.toAbsolutePath().toString());
- } else {
- String mavenPath = mavenToPath(lib.name);
- Path fallbackPath = librariesDir.resolve(mavenPath);
- if (Files.exists(fallbackPath)) {
- paths.add(fallbackPath.toAbsolutePath().toString());
- } else {
- System.out.println(ZAnsi.yellow(" Библиотека не найдена: " + lib.name));
- }
- }
- }
-
- Path versionJar = findVersionJar();
- if (versionJar != null) {
- paths.add(0, versionJar.toAbsolutePath().toString());
- }
-
- String separator = System.getProperty("os.name").toLowerCase().contains("win") ? ";" : ":";
- return String.join(separator, paths);
- }
-
- private String buildVanillaClasspath() throws Exception {
- List paths = new ArrayList<>();
- String versionId = getVersionId();
- Path versionJar = instance.getPath()
- .resolve("versions")
- .resolve(versionId)
- .resolve(versionId + ".jar");
-
- if (Files.exists(versionJar)) {
- paths.add(versionJar.toAbsolutePath().toString());
- } else {
- String mcVersion = instance.getMinecraftVersion();
- Path fallbackJar = instance.getPath()
- .resolve("versions")
- .resolve(mcVersion)
- .resolve(mcVersion + ".jar");
- if (Files.exists(fallbackJar)) {
- paths.add(fallbackJar.toAbsolutePath().toString());
- }
- }
-
- Path librariesDir = instance.getPath().resolve("libraries");
- if (Files.exists(librariesDir)) {
- try (var stream = Files.walk(librariesDir)) {
- stream.filter(p -> p.toString().endsWith(".jar"))
- .map(p -> p.toAbsolutePath().toString())
- .forEach(paths::add);
- }
- }
-
- String separator = System.getProperty("os.name").toLowerCase().contains("win") ? ";" : ":";
- return String.join(separator, paths);
- }
-
- private Path findVersionJar() {
- String versionId = getVersionId();
- Path versionsDir = instance.getPath().resolve("versions");
-
- Path[] candidates = {
- versionsDir.resolve(versionId).resolve(versionId + ".jar"),
- versionsDir.resolve(instance.getMinecraftVersion()).resolve(instance.getMinecraftVersion() + ".jar")
- };
-
- for (Path candidate : candidates) {
- if (Files.exists(candidate)) {
- return candidate;
- }
- }
-
- try {
- if (Files.exists(versionsDir)) {
- try (var stream = Files.list(versionsDir)) {
- return stream
- .filter(Files::isDirectory)
- .filter(dir -> dir.getFileName().toString().contains("forge") ||
- dir.getFileName().toString().contains("neoforge"))
- .filter(dir -> dir.getFileName().toString().contains(instance.getMinecraftVersion()))
- .findFirst()
- .map(dir -> dir.resolve(dir.getFileName().toString() + ".jar"))
- .filter(Files::exists)
- .orElse(null);
- }
- }
- } catch (Exception ignored) {}
-
- return null;
- }
-
- private String mavenToPath(String mavenName) {
- String[] parts = mavenName.split(":");
- if (parts.length < 3) return mavenName;
-
- String group = parts[0].replace('.', '/');
- String artifact = parts[1];
- String version = parts[2];
-
- if (parts.length == 4) {
- String classifier = parts[3];
- return group + "/" + artifact + "/" + version + "/" + artifact + "-" + version + "-" + classifier + ".jar";
- }
-
- return group + "/" + artifact + "/" + version + "/" + artifact + "-" + version + ".jar";
- }
-
- private List getJvmArguments(LaunchOptions options) {
- List jvmArgs = new ArrayList<>();
-
- int ramMB = options.getMaxMemory() > 0 ? options.getMaxMemory() : 4096;
- jvmArgs.add("-Xmx" + ramMB + "M");
- jvmArgs.add("-Xms" + Math.max(512, ramMB / 2) + "M");
-
- jvmArgs.add("-XX:+UseG1GC");
- jvmArgs.add("-XX:+UnlockExperimentalVMOptions");
- jvmArgs.add("-XX:G1NewSizePercent=20");
- jvmArgs.add("-XX:G1ReservePercent=20");
- jvmArgs.add("-XX:MaxGCPauseMillis=50");
- jvmArgs.add("-XX:G1HeapRegionSize=32M");
-
- String loaderType = instance.getLoaderType().toLowerCase();
-
- if ("fabric".equals(loaderType)) {
- jvmArgs.add("--add-modules=ALL-MODULE-PATH");
- jvmArgs.add("--add-opens=java.base/java.io=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.util=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.lang=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.lang.invoke=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.nio=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.net=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/sun.nio.ch=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.lang.reflect=ALL-UNNAMED");
- jvmArgs.add("--add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED");
- } else if ("forge".equals(loaderType)) {
- jvmArgs.add("--add-modules=ALL-MODULE-PATH");
- jvmArgs.add("--add-opens=java.base/java.util.jar=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.lang.invoke=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.lang.reflect=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.io=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.nio=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.net=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.util=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/sun.nio.ch=ALL-UNNAMED");
- jvmArgs.add("--add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED");
- } else if ("neoforge".equals(loaderType)) {
- jvmArgs.add("--add-modules=ALL-MODULE-PATH");
- jvmArgs.add("--add-opens=java.base/java.util.jar=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.lang.invoke=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.lang.reflect=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.io=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.nio=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.net=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/java.util=ALL-UNNAMED");
- jvmArgs.add("--add-opens=java.base/sun.nio.ch=ALL-UNNAMED");
- jvmArgs.add("--add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED");
- }
-
- if (options.getExtraJvmArgs() != null && !options.getExtraJvmArgs().isEmpty()) {
- jvmArgs.addAll(options.getExtraJvmArgs());
- }
-
- return jvmArgs;
- }
-}
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/launch/VersionManifest.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/launch/VersionManifest.java
deleted file mode 100644
index 2fdea9c..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/launch/VersionManifest.java
+++ /dev/null
@@ -1,165 +0,0 @@
-package me.sashegdev.zernmc.launcher.minecraft.launch;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class VersionManifest {
-
- private final String id;
- private final String mainClass;
- private final String assetIndexId;
- private final List jvmArguments;
- private final List gameArguments;
- private final List libraries;
-
- public VersionManifest(JSONObject json) {
- this.id = json.getString("id");
- this.mainClass = json.getString("mainClass");
-
- if (json.has("assetIndex")) {
- JSONObject ai = json.getJSONObject("assetIndex");
- this.assetIndexId = ai.has("id") ? ai.getString("id") : "unknown";
- } else {
- this.assetIndexId = "unknown";
- }
-
- this.jvmArguments = parseArguments(json, "jvm");
- this.gameArguments = parseArguments(json, "game");
- this.libraries = parseLibraries(json);
- }
-
- public String getId() { return id; }
- public String getMainClass() { return mainClass; }
- public String getAssetIndexId() { return assetIndexId; }
- public List getJvmArguments() { return jvmArguments; }
- public List getGameArguments() { return gameArguments; }
- public List getLibraries() { return libraries; }
-
- private List parseArguments(JSONObject json, String type) {
- List args = new ArrayList<>();
- if (!json.has("arguments")) return args;
-
- JSONObject arguments = json.getJSONObject("arguments");
- if (!arguments.has(type)) return args;
-
- JSONArray arr = arguments.getJSONArray(type);
- for (int i = 0; i < arr.length(); i++) {
- Object item = arr.get(i);
- if (item instanceof String) {
- args.add((String) item);
- } else if (item instanceof JSONObject) {
- JSONObject ruleObj = (JSONObject) item;
- if (ruleMatches(ruleObj)) {
- Object value = ruleObj.get("value");
- if (value instanceof String) {
- args.add((String) value);
- } else if (value instanceof JSONArray) {
- JSONArray valArr = (JSONArray) value;
- for (int j = 0; j < valArr.length(); j++) {
- args.add(valArr.getString(j));
- }
- }
- }
- }
- }
- return args;
- }
-
- private boolean ruleMatches(JSONObject ruleObj) {
- JSONArray rules = ruleObj.getJSONArray("rules");
- boolean result = false;
- for (int i = 0; i < rules.length(); i++) {
- JSONObject rule = rules.getJSONObject(i);
- String action = rule.getString("action");
- boolean matches = true;
-
- if (rule.has("os")) {
- JSONObject os = rule.getJSONObject("os");
- String osName = System.getProperty("os.name").toLowerCase();
- if (os.has("name")) {
- String reqName = os.getString("name").toLowerCase();
- if (reqName.equals("windows") && !osName.contains("win")) matches = false;
- else if (reqName.equals("linux") && !osName.contains("linux") && !osName.contains("nix")) matches = false;
- else if (reqName.equals("osx") && !osName.contains("mac")) matches = false;
- }
- if (os.has("arch")) {
- String reqArch = os.getString("arch");
- String osArch = System.getProperty("os.arch");
- if (!reqArch.equals(osArch)) matches = false;
- }
- }
-
- if (rule.has("features")) {
- JSONObject features = rule.getJSONObject("features");
- for (String key : features.keySet()) {
- if (key.startsWith("is_demo_user") || key.startsWith("has_custom_resolution")) continue;
- matches = false;
- }
- }
-
- if ("allow".equals(action) && matches) {
- result = true;
- } else if ("disallow".equals(action) && matches) {
- return false;
- }
- }
- return result;
- }
-
- private List parseLibraries(JSONObject json) {
- List libs = new ArrayList<>();
- if (!json.has("libraries")) return libs;
-
- JSONArray arr = json.getJSONArray("libraries");
- for (int i = 0; i < arr.length(); i++) {
- JSONObject libJson = arr.getJSONObject(i);
- if (libJson.has("downloads") && libJson.getJSONObject("downloads").has("artifact")) {
- String name = libJson.getString("name");
- String artifactPath = libJson.getJSONObject("downloads").getJSONObject("artifact").getString("path");
- Library lib = new Library(name, artifactPath);
-
- if (libJson.has("natives")) {
- JSONObject natives = libJson.getJSONObject("natives");
- for (String key : natives.keySet()) {
- String osKey = key.toLowerCase();
- lib.natives.put(osKey, natives.getString(key));
- }
- }
-
- if (libJson.has("rules")) {
- JSONObject dummyObj = new JSONObject();
- dummyObj.put("rules", libJson.getJSONArray("rules"));
- dummyObj.put("value", "");
- if (ruleMatches(dummyObj)) {
- libs.add(lib);
- }
- } else {
- libs.add(lib);
- }
- }
- }
- return libs;
- }
-
- public static class Library {
- public final String name;
- public final String artifactPath;
- public final Map natives = new HashMap<>();
-
- public Library(String name, String artifactPath) {
- this.name = name;
- this.artifactPath = artifactPath;
- }
-
- public String getSimpleName() {
- return name.substring(name.indexOf(':') + 1);
- }
- }
-}
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/model/LaunchOptions.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/model/LaunchOptions.java
deleted file mode 100644
index ea150b6..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/model/LaunchOptions.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package me.sashegdev.zernmc.launcher.minecraft.model;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class LaunchOptions {
- private String username = "Player";
- private String uuid = "00000000-0000-0000-0000-000000000000";
- private String accessToken = "token";
- private int maxMemory = 4096;
- private boolean fullscreen = false;
- private String javaPath = "java";
- private List extraJvmArgs = new ArrayList<>();
- private int width = 854;
- private int height = 480;
-
- // Геттеры и сеттеры
- public String getUsername() { return username; }
- public void setUsername(String username) { this.username = username; }
-
- public String getUuid() { return uuid; }
- public void setUuid(String uuid) { this.uuid = uuid; }
-
- public String getAccessToken() { return accessToken; }
- public void setAccessToken(String accessToken) { this.accessToken = accessToken; }
-
- public int getMaxMemory() { return maxMemory; }
- public void setMaxMemory(int maxMemory) { this.maxMemory = maxMemory; }
-
- public boolean isFullscreen() { return fullscreen; }
- public void setFullscreen(boolean fullscreen) { this.fullscreen = fullscreen; }
-
- public String getJavaPath() { return javaPath; }
- public void setJavaPath(String javaPath) { this.javaPath = javaPath; }
-
- public List getExtraJvmArgs() { return extraJvmArgs; }
- public void setExtraJvmArgs(List extraJvmArgs) { this.extraJvmArgs = extraJvmArgs; }
-
- public int getWidth() { return width; }
- public int getHeight() { return height; }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/model/MinecraftVersion.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/model/MinecraftVersion.java
deleted file mode 100644
index 9e5dcc6..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/minecraft/model/MinecraftVersion.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package me.sashegdev.zernmc.launcher.minecraft.model;
-
-import java.time.LocalDateTime;
-
-public class MinecraftVersion {
- private final String id;
- private final String type; // release, snapshot, old_beta, old_alpha
- private final LocalDateTime releaseTime;
- private final String url;
-
- public MinecraftVersion(String id, String type, LocalDateTime releaseTime, String url) {
- this.id = id;
- this.type = type;
- this.releaseTime = releaseTime;
- this.url = url;
- }
-
- public String getId() { return id; }
- public String getType() { return type; }
- public LocalDateTime getReleaseTime() { return releaseTime; }
- public String getUrl() { return url; }
-
- @Override
- public String toString() {
- return id + " (" + type + ")";
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/ArrowMenu.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/ArrowMenu.java
deleted file mode 100644
index 1a1720c..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/ArrowMenu.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package me.sashegdev.zernmc.launcher.ui;
-
-import me.sashegdev.zernmc.launcher.utils.ZAnsi;
-import org.jline.terminal.Terminal;
-import org.jline.terminal.TerminalBuilder;
-import org.jline.utils.InfoCmp;
-
-import java.io.IOException;
-import java.util.List;
-
-public class ArrowMenu {
-
- private final String title;
- private final List options;
- private int selected = 0;
- private final Terminal terminal;
-
- private static final int VISIBLE_ITEMS = 7; // сколько строк показывать в списке
-
- public ArrowMenu(String title, List options) throws IOException {
- this.title = title;
- this.options = options;
- this.terminal = TerminalBuilder.builder()
- .system(true)
- .jna(true)
- .build();
- }
-
- public int show() throws IOException {
- terminal.enterRawMode();
- terminal.puts(InfoCmp.Capability.clear_screen);
- terminal.puts(InfoCmp.Capability.cursor_invisible);
-
- try {
- while (true) {
- printPagedMenu();
- int key = terminal.reader().read();
-
- if (key == 'w' || key == 'W' || key == 'ц' || key == 'Ц'
- || key == 'k' || key == 'K' || key == 'л' || key == 'Л') { // Up / Arrow Up
- selected = (selected - 1 + options.size()) % options.size();
- }
- else if (key == 's' || key == 'S' || key == 'ы' || key == 'Ы'
- || key == 'j' || key == 'J' || key == 'о' || key == 'О') { // Down / Arrow Down
- selected = (selected + 1) % options.size();
- }
- else if (key == 13 || key == 10) { // Enter
- return selected;
- }
- else if (key == 27) { // Esc or arrow escape seq
- int next = terminal.reader().read(50);
- if (next == 91) { // '[' — start of arrow escape sequence
- int arrow = terminal.reader().read(50);
- if (arrow == 65) { // 'A' — Up arrow
- selected = (selected - 1 + options.size()) % options.size();
- } else if (arrow == 66) { // 'B' — Down arrow
- selected = (selected + 1) % options.size();
- }
- // else — unknown escape seq, ignore
- } else {
- return -1; // genuine Esc
- }
- }
- }
- } finally {
- terminal.puts(InfoCmp.Capability.cursor_visible);
- terminal.close();
- }
- }
-
- private void printPagedMenu() {
- StringBuilder sb = new StringBuilder();
- sb.append("\033[H\033[2J");
-
- // Заголовок (фиксированный)
- sb.append(ZAnsi.header("=== ZernMC Launcher ===")).append("\n\n");
- sb.append(ZAnsi.yellow(title)).append("\n\n");
-
- // Вычисляем диапазон отображаемых элементов
- int start = Math.max(0, selected - (VISIBLE_ITEMS / 2));
- int end = Math.min(options.size(), start + VISIBLE_ITEMS);
-
- // Если в конце списка — подтягиваем вверх
- if (end - start < VISIBLE_ITEMS && start > 0) {
- start = Math.max(0, end - VISIBLE_ITEMS);
- }
-
- for (int i = start; i < end; i++) {
- String line = options.get(i);
- if (i == selected) {
- sb.append(ZAnsi.selected(line)).append("\n");
- } else {
- sb.append(ZAnsi.white(" " + line)).append("\n");
- }
- }
-
- // Подсказка внизу (фиксированная)
- sb.append("\n")
- .append(ZAnsi.white("W/S (Ц/Ы) или ↑/↓ - перемещение | Enter - выбрать | Esc - назад"));
-
- System.out.print(sb);
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/LaunchServer.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/LaunchServer.java
deleted file mode 100644
index 2bb939f..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/LaunchServer.java
+++ /dev/null
@@ -1,254 +0,0 @@
-package me.sashegdev.zernmc.launcher.ui.jcef;
-
-import com.google.gson.Gson;
-import com.sun.net.httpserver.HttpServer;
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.Headers;
-import me.sashegdev.zernmc.launcher.api.ApiResponse;
-import me.sashegdev.zernmc.launcher.api.LauncherAPI;
-import me.sashegdev.zernmc.launcher.auth.AuthManager;
-import me.sashegdev.zernmc.launcher.minecraft.Instance;
-import me.sashegdev.zernmc.launcher.minecraft.InstanceManager;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executors;
-
-public class LaunchServer {
- private static final int PORT = 8080;
- private final LauncherAPI api;
- private final UIBridge bridge;
- private HttpServer server;
- private final Gson gson = new Gson();
-
- public LaunchServer(UIBridge bridge) {
- this.api = new LauncherAPI();
- this.bridge = bridge;
- }
-
- public void start() throws IOException {
- server = HttpServer.create(new InetSocketAddress("localhost", PORT), 0);
-
- server.createContext("/api/login", this::handleLogin);
- server.createContext("/api/account", this::handleAccount);
- server.createContext("/api/instances", this::handleInstances);
- server.createContext("/api/launch", this::handleLaunch);
- server.createContext("/api/install", this::handleInstall);
- server.createContext("/api/logs", this::handleLogs);
- server.createContext("/api/exit", this::handleExit);
- server.createContext("/ui/", this::handleStatic);
-
- server.setExecutor(Executors.newCachedThreadPool());
- server.start();
-
- bridge.log("HTTP сервер запущен на порту " + PORT);
- }
-
- public void stop() {
- if (server != null) {
- server.stop(0);
- }
- }
-
- private void handleLogin(HttpExchange exchange) throws IOException {
- if (!"POST".equals(exchange.getRequestMethod())) {
- sendJson(exchange, ApiResponse.error("Метод не поддерживается"));
- return;
- }
-
- try {
- Map body = parseJson(exchange.getRequestBody());
- String username = body.get("username");
- String password = body.get("password");
-
- var result = api.login(username, password);
- if (result.isSuccess()) {
- Map data = new HashMap<>();
- data.put("username", result.getData().getUsername());
- data.put("token", result.getData().getToken());
- sendJson(exchange, ApiResponse.success(data));
- bridge.log("Пользователь вошел: " + username);
- } else {
- sendJson(exchange, ApiResponse.error(result.getError()));
- }
- } catch (Exception e) {
- sendJson(exchange, ApiResponse.error("Ошибка: " + e.getMessage()));
- }
- }
-
- private void handleAccount(HttpExchange exchange) throws IOException {
- if (!"GET".equals(exchange.getRequestMethod())) {
- sendJson(exchange, ApiResponse.error("Метод не поддерживается"));
- return;
- }
-
- if (!api.isLoggedIn()) {
- sendJson(exchange, ApiResponse.error("Не авторизован"));
- return;
- }
-
- try {
- Map data = new HashMap<>();
- data.put("username", api.getCurrentUsername());
- data.put("passActive", AuthManager.hasActivePass());
- sendJson(exchange, ApiResponse.success(data));
- } catch (Exception e) {
- sendJson(exchange, ApiResponse.error(e.getMessage()));
- }
- }
-
- private void handleInstances(HttpExchange exchange) throws IOException {
- if (!"GET".equals(exchange.getRequestMethod())) {
- sendJson(exchange, ApiResponse.error("Метод не поддерживается"));
- return;
- }
-
- try {
- var result = api.getAllInstances();
- sendJson(exchange, result);
- } catch (Exception e) {
- sendJson(exchange, ApiResponse.error(e.getMessage()));
- }
- }
-
- private void handleLaunch(HttpExchange exchange) throws IOException {
- if (!"POST".equals(exchange.getRequestMethod())) {
- sendJson(exchange, ApiResponse.error("Метод не поддерживается"));
- return;
- }
-
- if (!api.isLoggedIn()) {
- sendJson(exchange, ApiResponse.error("Не авторизован"));
- return;
- }
-
- try {
- Map body = parseJson(exchange.getRequestBody());
- String name = body.get("name");
-
- var result = api.launch(name);
- if (result.isSuccess()) {
- Map data = new HashMap<>();
- data.put("pid", result.getData().getPid());
- data.put("status", result.getData().getStatus());
- sendJson(exchange, ApiResponse.success(data));
- bridge.log("Запущена сборка: " + name);
- } else {
- sendJson(exchange, ApiResponse.error(result.getError()));
- }
- } catch (Exception e) {
- sendJson(exchange, ApiResponse.error("Ошибка запуска: " + e.getMessage()));
- }
- }
-
- private void handleInstall(HttpExchange exchange) throws IOException {
- if (!"POST".equals(exchange.getRequestMethod())) {
- sendJson(exchange, ApiResponse.error("Метод не поддерживается"));
- return;
- }
-
- if (!api.isLoggedIn()) {
- sendJson(exchange, ApiResponse.error("Не авторизован"));
- return;
- }
-
- try {
- Map body = parseJson(exchange.getRequestBody());
- String name = body.get("name");
- String version = body.get("version");
- String loader = body.get("loader");
-
- bridge.log("Установка сборки: " + name + " " + version + " " + loader);
-
- var createResult = api.instances().createInstance(name);
- if (!createResult.isSuccess()) {
- sendJson(exchange, ApiResponse.error(createResult.getError()));
- return;
- }
-
- Instance instance = InstanceManager.getInstance(name);
- if (instance != null) {
- instance.setMinecraftVersion(version);
- instance.setLoaderType(loader);
- }
-
- sendJson(exchange, ApiResponse.success(true));
- bridge.log("Сборка установлена: " + name);
- } catch (Exception e) {
- sendJson(exchange, ApiResponse.error("Ошибка установки: " + e.getMessage()));
- }
- }
-
- private void handleLogs(HttpExchange exchange) throws IOException {
- String logs = bridge.getLogs();
- sendJson(exchange, ApiResponse.success(logs));
- }
-
- private void handleExit(HttpExchange exchange) throws IOException {
- bridge.log("Завершение работы...");
- System.exit(0);
- }
-
- private void handleStatic(HttpExchange exchange) throws IOException {
- String path = exchange.getRequestURI().getPath();
- if (path.equals("/ui/") || path.equals("/ui")) {
- path = "/ui/index.html";
- }
-
- var resource = getClass().getResource(path);
-
- if (resource == null) {
- exchange.sendResponseHeaders(404, 0);
- exchange.close();
- return;
- }
-
- try {
- byte[] content = resource.openStream().readAllBytes();
- String contentType = getContentType(path);
-
- exchange.getResponseHeaders().set("Content-Type", contentType);
- exchange.sendResponseHeaders(200, content.length);
-
- OutputStream os = exchange.getResponseBody();
- os.write(content);
- os.close();
- } catch (IOException ignored) {}
- }
-
- private String getContentType(String path) {
- if (path.endsWith(".html")) return "text/html; charset=utf-8";
- if (path.endsWith(".css")) return "text/css; charset=utf-8";
- if (path.endsWith(".js")) return "application/javascript; charset=utf-8";
- return "text/plain";
- }
-
- @SuppressWarnings("unchecked")
- private Map parseJson(InputStream body) {
- try {
- String json = new String(body.readAllBytes(), StandardCharsets.UTF_8);
- return gson.fromJson(json, Map.class);
- } catch (Exception e) {
- return new HashMap<>();
- }
- }
-
- private void sendJson(HttpExchange exchange, ApiResponse response) {
- try {
- String json = gson.toJson(response);
- byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
-
- exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
- exchange.sendResponseHeaders(200, bytes.length);
-
- OutputStream os = exchange.getResponseBody();
- os.write(bytes);
- os.close();
- } catch (IOException ignored) {}
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/UIBridge.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/UIBridge.java
deleted file mode 100644
index 9768e80..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/UIBridge.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package me.sashegdev.zernmc.launcher.ui.jcef;
-
-public class UIBridge {
- public void log(String message) {
- System.out.println("[UI] " + message);
- }
-
- public String getLogs() {
- return "";
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/UILauncher.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/UILauncher.java
deleted file mode 100644
index 5c99b4f..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/ui/jcef/UILauncher.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package me.sashegdev.zernmc.launcher.ui.jcef;
-
-import me.sashegdev.zernmc.launcher.api.LauncherAPI;
-
-import java.awt.*;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-import java.net.URI;
-
-public class UILauncher {
- private static final String APP_TITLE = "ZernMC Launcher";
- private final LauncherAPI api;
- private final UIBridge bridge;
- private LaunchServer server;
-
- public UILauncher() {
- this.api = new LauncherAPI();
- this.bridge = new UIBridge();
- }
-
- public void launch() throws Exception {
- redirectSystemLogs();
- bridge.log("Запуск UI...");
-
- server = new LaunchServer(bridge);
- server.start();
-
- openBrowser();
-
- Runtime.getRuntime().addShutdownHook(new Thread(() -> {
- bridge.log("Выключение...");
- if (server != null) server.stop();
- }));
-
- Thread.currentThread().join();
- }
-
- private void openBrowser() {
- String url = "http://localhost:8080/ui/";
- bridge.log("Открытие браузера: " + url);
-
- try {
- if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
- Desktop.getDesktop().browse(URI.create(url));
- bridge.log("Браузер открыт");
- } else {
- bridge.log("Desktop browsing not supported");
- }
- } catch (Exception e) {
- bridge.log("Ошибка открытия браузера: " + e.getMessage());
- }
- }
-
- private void redirectSystemLogs() {
- PrintStream originalOut = System.out;
- PrintStream originalErr = System.err;
-
- System.setOut(new PrintStream(new ByteArrayOutputStream() {
- @Override
- public void write(byte[] b, int off, int len) {
- String line = new String(b, off, len).trim();
- if (!line.isEmpty()) {
- bridge.log(line);
- }
- try {
- originalOut.write(b, off, len);
- } catch (Exception ignored) {}
- }
-
- @Override
- public void write(int b) {
- try {
- originalOut.write(b);
- } catch (Exception ignored) {}
- }
- }));
-
- System.setErr(new PrintStream(new ByteArrayOutputStream() {
- @Override
- public void write(byte[] b, int off, int len) {
- String line = new String(b, off, len).trim();
- if (!line.isEmpty()) {
- bridge.log("[ERROR] " + line);
- }
- try {
- originalErr.write(b, off, len);
- } catch (Exception ignored) {}
- }
- }));
- }
-
- public static void main(String[] args) {
- try {
- UILauncher launcher = new UILauncher();
- launcher.launch();
- } catch (Exception e) {
- System.err.println("UI launch failed: " + e.getMessage());
- e.printStackTrace();
- System.exit(1);
- }
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Config.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Config.java
deleted file mode 100644
index c22793e..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Config.java
+++ /dev/null
@@ -1,137 +0,0 @@
-package me.sashegdev.zernmc.launcher.utils;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Properties;
-
-public class Config {
-
- private static final Path CONFIG_DIR = Path.of(System.getProperty("user.home"), ".zernmc");
- private static final Path CONFIG_FILE = CONFIG_DIR.resolve("launcher.properties");
-
- private static final String BUILD_PROFILE = System.getProperty("build.profile", "global");
-
- private static final Properties props = new Properties();
-
- // Настройки
- private static int maxMemory = 4096; // будет перезаписано умной логикой
- private static String serverUrl = "http://87.120.187.36:1582";
- private static String lastUsername = "Player";
-
- static {
- load();
- applySmartRamRecommendation();
- }
-
- private static void load() {
- try {
- Files.createDirectories(CONFIG_DIR);
- if (Files.exists(CONFIG_FILE)) {
- try (var is = Files.newInputStream(CONFIG_FILE)) {
- props.load(is);
- }
- }
-
- maxMemory = Integer.parseInt(props.getProperty("maxMemory", "4096"));
- serverUrl = props.getProperty("serverUrl", serverUrl);
- lastUsername = props.getProperty("lastUsername", lastUsername);
-
- } catch (Exception e) {
- System.err.println(ZAnsi.brightRed("Не удалось загрузить конфиг: ") + e.getMessage());
- }
- }
-
- public static void save() {
- try {
- props.setProperty("maxMemory", String.valueOf(maxMemory));
- props.setProperty("serverUrl", serverUrl);
- props.setProperty("lastUsername", lastUsername);
-
- try (var os = Files.newOutputStream(CONFIG_FILE)) {
- props.store(os, "ZernMC Launcher Configuration");
- }
- } catch (IOException e) {
- System.err.println(ZAnsi.brightRed("Не удалось сохранить конфиг: ") + e.getMessage());
- }
- }
-
- /**
- * Умная рекомендация RAM:
- * - минимум 1.5 GB
- * - рекомендуется totalRAM - 30%
- * - максимум 70% от доступной RAM
- */
- private static void applySmartRamRecommendation() {
- long totalRamMB = Runtime.getRuntime().maxMemory() / (1024 * 1024); // в MB
-
- // Рекомендуемое значение = total - 30%
- long recommended = (long) (totalRamMB * 0.70); // 70% от доступной
-
- // Ограничения
- recommended = Math.max(1536, recommended); // минимум 1.5 GB
- recommended = Math.min(recommended, totalRamMB - 1024); // оставляем минимум 1 GB системе
-
- // Если текущее значение сильно отличается от рекомендуемого — корректируем
- if (Math.abs(maxMemory - recommended) > 1024) { // разница больше 1 GB
- maxMemory = (int) recommended;
- save(); // сохраняем умную рекомендацию
- System.out.println(ZAnsi.cyan("Автоматически рекомендовано RAM: " + maxMemory + " MB"));
- }
- }
-
- // Getters & Setters
- public static int getMaxMemory() {
- return maxMemory;
- }
-
- public static boolean isZernMCBuild() {
- return "zernmc".equalsIgnoreCase(BUILD_PROFILE);
- }
-
- public static boolean isGlobalBuild() {
- return !isZernMCBuild();
- }
-
- public static void setMaxMemory(int memory) {
- // Защита от слишком маленьких/больших значений
- if (memory < 1024) memory = 1536;
- if (memory > 32768) memory = 32768;
-
- maxMemory = memory;
- save();
- }
-
- public static String getServerUrl() {
- return serverUrl;
- }
-
- public static String getLastUsername() {
- return lastUsername;
- }
-
- public static void setLastUsername(String username) {
- lastUsername = username;
- save();
- }
-
- public static Path getInstancesDir() {
- return CONFIG_DIR.resolve("instances");
- }
-
- public static Path getJreDir() {
- return CONFIG_DIR.resolve("jre");
- }
-
- public static Path getConfigDir() {
- return CONFIG_DIR;
- }
-
- /**
- * Полезная информация для пользователя
- */
- public static String getRamInfo() {
- long totalMB = Runtime.getRuntime().maxMemory() / (1024 * 1024);
- return "Доступно RAM: " + totalMB + " MB | Рекомендуется: " + maxMemory + " MB";
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ConsoleUtils.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ConsoleUtils.java
deleted file mode 100644
index bde04d3..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ConsoleUtils.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package me.sashegdev.zernmc.launcher.utils;
-
-import java.io.IOException;
-
-public class ConsoleUtils {
-
- public static void clearScreen() {
- System.out.print("\033[H\033[2J");
- System.out.flush();
- }
-
- public static void pause() {
- System.out.print(ZAnsi.white("\nНажмите Enter для продолжения..."));
- try {
- System.in.read();
- // Очищаем буфер ввода
- while (System.in.available() > 0) {
- System.in.read();
- }
- } catch (IOException ignored) {}
- }
-
- public static void printHeader(String subtitle) {
- clearScreen();
- System.out.println(ZAnsi.header("=== ZernMC Launcher ==="));
- if (subtitle != null && !subtitle.isEmpty()) {
- System.out.println(ZAnsi.yellow(subtitle));
- }
- System.out.println();
- }
-
- public static void printHeader() {
- printHeader(null);
- }
-
- public static void separator() {
- System.out.println(ZAnsi.white("────────────────────────────────────────────────────────────"));
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Input.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Input.java
deleted file mode 100644
index 988505e..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/Input.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package me.sashegdev.zernmc.launcher.utils;
-
-import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Scanner;
-
-/**
- * Улучшенный Input с поддержкой кириллицы и confirm через ArrowMenu
- */
-public class Input {
-
- // Используем UTF-8 явно — это помогает на Windows
- private static final Scanner scanner = new Scanner(System.in, "UTF-8");
-
- public static String readLine() {
- return scanner.nextLine().trim();
- }
-
- public static String readLine(String prompt) {
- flushInput(); // Очищаем буфер
- System.out.print(prompt);
- return scanner.nextLine().trim();
- }
-
- public static int readInt(String prompt) {
- while (true) {
- try {
- System.out.print(prompt);
- return Integer.parseInt(scanner.nextLine().trim());
- } catch (NumberFormatException e) {
- System.out.println(ZAnsi.brightRed("Некорректное число. Попробуйте ещё раз."));
- }
- }
- }
-
- public static int readInt(String prompt, int min, int max) {
- while (true) {
- int value = readInt(prompt);
- if (value >= min && value <= max) {
- return value;
- }
- System.out.println(ZAnsi.brightRed("Значение должно быть от " + min + " до " + max + "."));
- }
- }
-
- /**
- * Новый confirm через ArrowMenu
- * @throws IOException
- */
- public static boolean confirm(String question) throws IOException {
- ConsoleUtils.clearScreen(); // опционально, можно убрать
-
- List options = List.of(
- "Да",
- "Нет"
- );
-
- ArrowMenu menu = new ArrowMenu(question, options);
- int choice = menu.show();
-
- return choice == 0; // 0 = "Да"
- }
-
- /**
- * Альтернативный confirm без очистки экрана
- * @throws IOException
- */
- public static boolean confirmInline(String question) throws IOException {
- List options = List.of("Да", "Нет");
- ArrowMenu menu = new ArrowMenu(question, options);
- int choice = menu.show();
- return choice == 0;
- }
-
- /**
- * Закрытие сканнера (вызывать при выходе из программы, если нужно)
- */
- public static void close() {
- scanner.close();
- }
-
-
- /**
- * Очищает буфер ввода от оставшихся символов
- */
- public static void flushInput() {
- try {
- while (System.in.available() > 0) {
- System.in.read();
- }
- } catch (IOException e) {
- // Игнорируем
- }
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ProgressBar.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ProgressBar.java
deleted file mode 100644
index d85bb2d..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ProgressBar.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package me.sashegdev.zernmc.launcher.utils;
-
-import java.text.DecimalFormat;
-
-public class ProgressBar {
-
- private static final int BAR_LENGTH = 40;
- private static final DecimalFormat DF = new DecimalFormat("#.##");
-
- /**
- * Прогресс по количеству файлов (для библиотек и общего прогресса)
- */
- public static void show(String label, long current, long total, String unit) {
- if (total <= 0) {
- System.out.print("\r" + ZAnsi.cyan(label) + " ...");
- return;
- }
- double progress = (double) current / total;
- int filled = (int) (progress * BAR_LENGTH);
- String bar = "█".repeat(filled) + "░".repeat(BAR_LENGTH - filled);
- int percent = (int) (progress * 100);
-
- String text = String.format("%s [%s] %3d%% (%d/%d %s)",
- ZAnsi.cyan(label), bar, percent, current, total, unit);
-
- System.out.print("\r" + text);
- System.out.flush();
- }
-
- /**
- * Прогресс по байтам для одного файла (реальный прогресс)
- */
- public static void showDownload(String label, long downloaded, long totalBytes) {
- if (totalBytes <= 0) {
- System.out.print("\r" + ZAnsi.cyan(label) + " ...");
- return;
- }
-
- double progress = (double) downloaded / totalBytes;
- int filled = (int) (progress * BAR_LENGTH);
- String bar = "█".repeat(filled) + "░".repeat(BAR_LENGTH - filled);
- String percent = DF.format(progress * 100);
-
- String text = String.format("%s [%s] %6s%% %s / %s",
- ZAnsi.cyan(label),
- bar,
- percent,
- formatBytes(downloaded),
- formatBytes(totalBytes));
-
- System.out.print("\r" + text);
- System.out.flush();
- }
-
- public static void showAnimated(String label, long current, long total, String unit) {
- if (total <= 0) {
- // Анимация для неизвестного размера
- char[] spinner = {'|', '/', '-', '\\'};
- int idx = (int) (current / 1024) % 4;
- System.out.print("\r" + label + " [" + spinner[idx] + "] " + formatBytes(current));
- } else {
- show(label, (int) ((current * 100) / total), 100, unit);
- }
- }
-
- public static void finish(String message) {
- System.out.println("\r" + ZAnsi.brightGreen(message + " завершено ✓"));
- System.out.flush();
- }
-
- public static void clearLine() {
- System.out.print("\r" + " ".repeat(110) + "\r");
- System.out.flush();
- }
-
- public static String formatBytes(long bytes) {
- if (bytes < 1024) return bytes + " B";
- if (bytes < 1024 * 1024) return DF.format(bytes / 1024.0) + " KB";
- return DF.format(bytes / (1024.0 * 1024)) + " MB";
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ZAnsi.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ZAnsi.java
deleted file mode 100644
index 6575ca2..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ZAnsi.java
+++ /dev/null
@@ -1,182 +0,0 @@
-package me.sashegdev.zernmc.launcher.utils;
-
-import org.fusesource.jansi.Ansi;
-import org.fusesource.jansi.AnsiConsole;
-
-public class ZAnsi {
-
- //поддержка ANSI епта
- public static void install() {
- AnsiConsole.systemInstall();
- }
-
- public static void uninstall() {
- AnsiConsole.systemUninstall();
- }
-
- // === Основные цвета ===
- public static String green(String text) {
- return Ansi.ansi().fg(Ansi.Color.GREEN).a(text).reset().toString();
- }
-
- public static String brightGreen(String text) {
- return Ansi.ansi().fgBright(Ansi.Color.GREEN).a(text).reset().toString();
- }
-
- public static String cyan(String text) {
- return Ansi.ansi().fg(Ansi.Color.CYAN).a(text).reset().toString();
- }
-
- public static String brightCyan(String text) {
- return Ansi.ansi().fgBright(Ansi.Color.CYAN).a(text).reset().toString();
- }
-
- public static String yellow(String text) {
- return Ansi.ansi().fg(Ansi.Color.YELLOW).a(text).reset().toString();
- }
-
- public static String brightYellow(String text) {
- return Ansi.ansi().fgBright(Ansi.Color.YELLOW).a(text).reset().toString();
- }
-
- public static String red(String text) {
- return Ansi.ansi().fg(Ansi.Color.RED).a(text).reset().toString();
- }
-
- public static String brightRed(String text) {
- return Ansi.ansi().fgBright(Ansi.Color.RED).a(text).reset().toString();
- }
-
- public static String blue(String text) {
- return Ansi.ansi().fg(Ansi.Color.BLUE).a(text).reset().toString();
- }
-
- public static String brightBlue(String text) {
- return Ansi.ansi().fgBright(Ansi.Color.BLUE).a(text).reset().toString();
- }
-
- public static String magenta(String text) {
- return Ansi.ansi().fg(Ansi.Color.MAGENTA).a(text).reset().toString();
- }
-
- public static String brightMagenta(String text) {
- return Ansi.ansi().fgBright(Ansi.Color.MAGENTA).a(text).reset().toString();
- }
-
- // Пурпурный как brightPurple (используем magenta)
- public static String purple(String text) {
- return brightMagenta(text);
- }
-
- public static String brightPurple(String text) {
- return brightMagenta(text);
- }
-
- public static String white(String text) {
- return Ansi.ansi().fg(Ansi.Color.WHITE).a(text).reset().toString();
- }
-
- public static String brightWhite(String text) {
- return Ansi.ansi().fgBright(Ansi.Color.WHITE).a(text).reset().toString();
- }
-
- public static String black(String text) {
- return Ansi.ansi().fg(Ansi.Color.BLACK).a(text).reset().toString();
- }
-
- // === Фоновые цвета ===
- public static String bgGreen(String text) {
- return Ansi.ansi().bg(Ansi.Color.GREEN).a(text).reset().toString();
- }
-
- public static String bgRed(String text) {
- return Ansi.ansi().bg(Ansi.Color.RED).a(text).reset().toString();
- }
-
- public static String bgYellow(String text) {
- return Ansi.ansi().bg(Ansi.Color.YELLOW).a(text).reset().toString();
- }
-
- public static String bgBlue(String text) {
- return Ansi.ansi().bg(Ansi.Color.BLUE).a(text).reset().toString();
- }
-
- // === Стили ===
- public static String bold(String text) {
- return Ansi.ansi().bold().a(text).reset().toString();
- }
-
- public static String reset() {
- return Ansi.ansi().reset().toString();
- }
-
- // === Комбинированные удобные методы ===
- public static String header(String text) {
- return Ansi.ansi().fgBright(Ansi.Color.CYAN).bold().a(text).reset().toString();
- }
-
- public static String success(String text) {
- return Ansi.ansi().fgBright(Ansi.Color.GREEN).bold().a("[✓] " + text).reset().toString();
- }
-
- public static String error(String text) {
- return Ansi.ansi().fgBright(Ansi.Color.RED).bold().a("[✗] " + text).reset().toString();
- }
-
- public static String warning(String text) {
- return Ansi.ansi().fgBright(Ansi.Color.YELLOW).bold().a("[!] " + text).reset().toString();
- }
-
- public static String info(String text) {
- return Ansi.ansi().fgBright(Ansi.Color.CYAN).bold().a("[i] " + text).reset().toString();
- }
-
- public static String selected(String text) {
- return Ansi.ansi()
- .bgBright(Ansi.Color.WHITE)
- .fg(Ansi.Color.BLACK)
- .bold()
- .a(" > " + text + " ")
- .reset()
- .toString();
- }
-
- public static String dim(String text) {
- return Ansi.ansi().fgBright(Ansi.Color.BLACK).a(text).reset().toString();
- }
-
- // === Цветной текст для ролей ===
- public static String roleUser(String text) {
- return white(text);
- }
-
- public static String rolePassHolder(String text) {
- return brightGreen(text);
- }
-
- public static String roleModerator(String text) {
- return brightBlue(text);
- }
-
- public static String roleElder(String text) {
- return brightPurple(text);
- }
-
- public static String roleCreator(String text) {
- return brightRed(text);
- }
-
- // === Очистка экрана ===
- public static String clearScreen() {
- return Ansi.ansi().eraseScreen().cursor(1, 1).toString();
- }
-
- // === Прогресс бар символы ===
- public static String progressChar() {
- return Ansi.ansi().fgBright(Ansi.Color.CYAN).a("█").reset().toString();
- }
-
- public static String progressEmpty() {
- return Ansi.ansi().fg(Ansi.Color.BLACK).a("░").reset().toString();
- }
-}
\ No newline at end of file
diff --git a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ZHttpClient.java b/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ZHttpClient.java
deleted file mode 100644
index bf4e848..0000000
--- a/launcher/src/main/java/me/sashegdev/zernmc/launcher/utils/ZHttpClient.java
+++ /dev/null
@@ -1,550 +0,0 @@
-package me.sashegdev.zernmc.launcher.utils;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-import me.sashegdev.zernmc.launcher.auth.AuthManager;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URLEncoder;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public class ZHttpClient {
-
- private static final HttpClient client = HttpClient.newBuilder()
- .connectTimeout(Duration.ofSeconds(15))
- .version(HttpClient.Version.HTTP_1_1)
- .build();
-
- private static String BASE_URL = "http://87.120.187.36:1582";
-
- // Глобальный прокси режим (для обратной совместимости)
- private static final AtomicBoolean useProxyMode = new AtomicBoolean(false);
- private static final AtomicBoolean proxyTested = new AtomicBoolean(false);
-
- /**
- * Переопределить URL сервера (для тестов).
- * Внимание: не потокобезопасно, использовать только в тестах.
- */
- public static void setBaseUrl(String url) {
- BASE_URL = url;
- }
-
- public static String getBaseUrl() {
- return BASE_URL;
- }
-
- // Умное проксирование по сервисам
- public enum ServiceType {
- ZERN_SERVER("http://87.120.187.36:1582", true),
- FABRIC_META("https://meta.fabricmc.net", false),
- FABRIC_MAVEN("https://maven.fabricmc.net", false),
- MOJANG_META("https://piston-meta.mojang.com", false),
- MOJANG_RESOURCES("https://resources.download.minecraft.net", false),
- FORGE_MAVEN("https://maven.minecraftforge.net", false),
- NEOFORGE_MAVEN("https://maven.neoforged.net", false),
- GOOGLE("https://google.com", false),
- CLOUDFLARE("https://cloudflare.com", false);
-
- private final String baseUrl;
- private final boolean alwaysDirect;
-
- ServiceType(String baseUrl, boolean alwaysDirect) {
- this.baseUrl = baseUrl;
- this.alwaysDirect = alwaysDirect;
- }
-
- public String getBaseUrl() { return baseUrl; }
- public boolean isAlwaysDirect() { return alwaysDirect; }
- }
-
- // Статусы сервисов
- private static final Map serviceProxyMode = new ConcurrentHashMap<>();
- private static final Map serviceFailCount = new ConcurrentHashMap<>();
- private static final Map serviceLastCheckTime = new ConcurrentHashMap<>();
- private static final Map serviceHealthy = new ConcurrentHashMap<>();
-
- private static final int MAX_FAILS_BEFORE_PROXY = 2;
- private static final long HEALTH_CHECK_INTERVAL_MS = 60000; // 1 минута
- private static final long CHECK_TIMEOUT_MS = 7000; // 7 секунд на проверку
-
- // Статистика
- private static int directSuccessCount = 0;
- private static int proxySuccessCount = 0;
- private static int directFailCount = 0;
-
- static {
- for (ServiceType type : ServiceType.values()) {
- serviceProxyMode.put(type, false);
- serviceFailCount.put(type, 0);
- serviceHealthy.put(type, false);
- }
- }
-
- /**
- * Вызывать один раз при запуске лаунчера
- */
- public static void checkAllServicesOnStartup() {
- if (proxyTested.get()) return;
-
- System.out.println(ZAnsi.cyan("Проверка доступности сервисов..."));
-
- List servicesToCheck = List.of(
- ServiceType.ZERN_SERVER,
- ServiceType.GOOGLE,
- ServiceType.FABRIC_META,
- ServiceType.FABRIC_MAVEN,
- ServiceType.MOJANG_META,
- ServiceType.MOJANG_RESOURCES,
- ServiceType.FORGE_MAVEN,
- ServiceType.NEOFORGE_MAVEN
- );
-
- for (ServiceType service : servicesToCheck) {
- boolean isHealthy = checkServiceHealth(service);
- serviceHealthy.put(service, isHealthy);
-
- if (service.isAlwaysDirect()) {
- System.out.println(isHealthy ?
- ZAnsi.green(" " + service.name() + " - OK") :
- ZAnsi.red(" " + service.name() + " - НЕ ДОСТУПЕН (критично!)"));
- } else {
- if (isHealthy) {
- System.out.println(ZAnsi.green(" " + service.name() + " - прямое подключение работает"));
- } else {
- System.out.println(ZAnsi.yellow(" " + service.name() + " - НЕ ДОСТУПЕН, будет использован прокси"));
- serviceProxyMode.put(service, true);
- serviceFailCount.put(service, MAX_FAILS_BEFORE_PROXY);
- }
- }
- }
-
- if (!serviceHealthy.get(ServiceType.ZERN_SERVER)) {
- System.out.println(ZAnsi.brightRed("Критическая ошибка: Zern сервер недоступен!"));
- }
-
- proxyTested.set(true);
- startHealthCheckThread();
- printStats();
- }
-
- /**
- * Принудительная проверка Mojang-сервисов (рекомендуется вызывать перед установкой сборки)
- */
- public static void forceCheckMojangServices() {
- System.out.println(ZAnsi.cyan("Принудительная проверка Mojang сервисов..."));
-
- for (ServiceType service : List.of(ServiceType.MOJANG_META, ServiceType.MOJANG_RESOURCES)) {
- boolean healthy = checkServiceHealth(service);
- serviceHealthy.put(service, healthy);
-
- if (healthy) {
- System.out.println(ZAnsi.green(" " + service.name() + " доступен напрямую"));
- serviceProxyMode.put(service, false);
- serviceFailCount.put(service, 0);
- } else {
- System.out.println(ZAnsi.yellow(" " + service.name() + " недоступен → прокси режим активирован"));
- serviceProxyMode.put(service, true);
- serviceFailCount.put(service, MAX_FAILS_BEFORE_PROXY);
- }
- }
- }
-
- private static boolean checkServiceHealth(ServiceType service) {
- return checkDirectConnection(service.getBaseUrl());
- }
-
- /**
- * Улучшенная проверка прямого подключения
- */
- private static boolean checkDirectConnection(String baseUrl) {
- String testUrl = baseUrl;
-
- if (baseUrl.contains("piston-meta.mojang.com") || baseUrl.contains("launchermeta.mojang.com")) {
- testUrl = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
- } else if (baseUrl.contains("resources.download.minecraft.net")) {
- testUrl = "https://resources.download.minecraft.net/00/0000000000000000000000000000000000000000";
- }
-
- try {
- HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create(testUrl))
- .timeout(Duration.ofMillis(CHECK_TIMEOUT_MS))
- .GET()
- .header("User-Agent", "ZernMC-Launcher/HealthCheck")
- .build();
-
- HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
- int code = response.statusCode();
- return code == 200 || code == 404; // 404 для ресурсов — нормально
- } catch (Exception e) {
- return false;
- }
- }
-
- private static void startHealthCheckThread() {
- Thread healthThread = new Thread(() -> {
- while (true) {
- try {
- Thread.sleep(HEALTH_CHECK_INTERVAL_MS);
- performHealthCheck();
- } catch (InterruptedException e) {
- break;
- }
- }
- });
- healthThread.setDaemon(true);
- healthThread.start();
- }
-
- private static void performHealthCheck() {
- for (ServiceType service : ServiceType.values()) {
- if (service.isAlwaysDirect()) continue;
-
- boolean isHealthy = checkServiceHealth(service);
- serviceHealthy.put(service, isHealthy);
-
- if (isHealthy && serviceProxyMode.get(service)) {
- serviceProxyMode.put(service, false);
- serviceFailCount.put(service, 0);
- System.out.println(ZAnsi.green("[NET] " + service.name() + " восстановлен, переключен на прямое подключение"));
- } else if (!isHealthy && !serviceProxyMode.get(service)) {
- int fails = serviceFailCount.getOrDefault(service, 0) + 1;
- serviceFailCount.put(service, fails);
- serviceLastCheckTime.put(service, System.currentTimeMillis());
-
- if (fails >= MAX_FAILS_BEFORE_PROXY) {
- serviceProxyMode.put(service, true);
- System.out.println(ZAnsi.yellow("[NET] " + service.name() + " недоступен, включен прокси режим"));
- }
- }
- }
- }
-
- private static ServiceType detectService(String url) {
- if (url.contains("meta.fabricmc.net")) return ServiceType.FABRIC_META;
- if (url.contains("maven.fabricmc.net")) return ServiceType.FABRIC_MAVEN;
- if (url.contains("piston-meta.mojang.com") || url.contains("launchermeta.mojang.com"))
- return ServiceType.MOJANG_META;
- if (url.contains("resources.download.minecraft.net")) return ServiceType.MOJANG_RESOURCES;
- if (url.contains("maven.minecraftforge.net")) return ServiceType.FORGE_MAVEN;
- if (url.contains("maven.neoforged.net")) return ServiceType.NEOFORGE_MAVEN;
- if (url.contains("google.com")) return ServiceType.GOOGLE;
- if (url.contains("cloudflare.com")) return ServiceType.CLOUDFLARE;
- return null;
- }
-
- private static boolean shouldUseProxyForUrl(String url) {
- if (useProxyMode.get()) return true;
-
- ServiceType service = detectService(url);
- if (service == null || service.isAlwaysDirect()) return false;
-
- return serviceProxyMode.getOrDefault(service, false);
- }
-
- private static boolean isConnectionError(Throwable e) {
- Throwable cause = e.getCause() != null ? e.getCause() : e;
- String msg = cause.getMessage() != null ? cause.getMessage().toLowerCase() : "";
-
- return cause instanceof java.net.ConnectException ||
- cause instanceof java.net.UnknownHostException ||
- cause instanceof java.nio.channels.ClosedChannelException ||
- msg.contains("connection") ||
- msg.contains("timeout") ||
- msg.contains("refused") ||
- msg.contains("closed");
- }
-
- private static void markServiceAsBlocked(String url) {
- ServiceType service = detectService(url);
- if (service == null || service.isAlwaysDirect()) return;
-
- int fails = serviceFailCount.getOrDefault(service, 0) + 1;
- serviceFailCount.put(service, fails);
- serviceLastCheckTime.put(service, System.currentTimeMillis());
-
- if (fails >= MAX_FAILS_BEFORE_PROXY && !serviceProxyMode.get(service)) {
- serviceProxyMode.put(service, true);
- System.out.println(ZAnsi.yellow("[NET] " + service.name() + " заблокирован, переключаемся на прокси"));
- }
- }
- /**
- * Универсальный GET с умным прокси + автоматическим fallback
- */
- public static String getWithSmartProxy(String url) throws IOException, InterruptedException {
- // Попытка прямого подключения
- if (!shouldUseProxyForUrl(url)) {
- try {
- HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create(url))
- .timeout(Duration.ofSeconds(25))
- .header("User-Agent", "ZernMC-Launcher/1.0")
- .GET()
- .build();
-
- HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
-
- if (response.statusCode() == 200) {
- directSuccessCount++;
- return response.body();
- }
-
- if (response.statusCode() >= 400) {
- throw new IOException("HTTP " + response.statusCode());
- }
- } catch (Exception e) {
- if (isConnectionError(e)) {
- directFailCount++;
- markServiceAsBlocked(url);
- }
- // Если ошибка соединения — пробуем через прокси
- }
- }
-
- // Через прокси
- try {
- String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);
- String proxyUrl = BASE_URL + "/download?url=" + encodedUrl;
-
- HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create(proxyUrl))
- .timeout(Duration.ofSeconds(40))
- .header("User-Agent", "ZernMC-Launcher/1.0")
- .GET()
- .build();
-
- HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
-
- if (response.statusCode() != 200) {
- throw new IOException("Proxy HTTP " + response.statusCode());
- }
-
- proxySuccessCount++;
- return response.body();
-
- } catch (Exception e) {
- throw new IOException("Не удалось получить данные ни напрямую, ни через прокси: " + e.getMessage(), e);
- }
- }
-
- /**
- * Скачивание файла с умным прокси + fallback
- */
- public static void downloadFileWithSmartProxy(String url, Path target) throws Exception {
- if (!shouldUseProxyForUrl(url)) {
- try {
- HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create(url))
- .timeout(Duration.ofSeconds(40))
- .header("User-Agent", "ZernMC-Launcher/1.0")
- .GET()
- .build();
-
- HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofFile(target));
-
- if (response.statusCode() == 200) {
- directSuccessCount++;
- return;
- }
- } catch (Exception e) {
- if (isConnectionError(e)) {
- directFailCount++;
- markServiceAsBlocked(url);
- }
- // fallback на прокси ниже
- }
- }
-
- // Скачивание через прокси
- String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);
- String proxyUrl = BASE_URL + "/proxy/download?url=" + encodedUrl;
-
- HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create(proxyUrl))
- .timeout(Duration.ofMinutes(5))
- .header("User-Agent", "ZernMC-Launcher/1.0")
- .GET()
- .build();
-
- HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofFile(target));
-
- if (response.statusCode() != 200) {
- throw new IOException("Proxy download failed: HTTP " + response.statusCode());
- }
-
- proxySuccessCount++;
- }
-
- // ====================== СТАРЫЕ МЕТОДЫ (обновлённые) ======================
-
- public static String get(String endpoint) throws IOException, InterruptedException {
- checkAllServicesOnStartup();
-
- if (useProxyMode.get()) {
- return proxyGet(endpoint);
- }
-
- try {
- HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
- .uri(URI.create(BASE_URL + endpoint))
- .timeout(Duration.ofSeconds(15))
- .header("User-Agent", "ZernMC-Launcher/1.0")
- .GET();
-
- // ===== ДОБАВИТЬ ТОКЕН АВТОРИЗАЦИИ =====
- String accessToken = AuthManager.getAccessToken();
- if (accessToken != null && !accessToken.equals("0")) {
- requestBuilder.header("Authorization", "Bearer " + accessToken);
- }
-
- HttpRequest request = requestBuilder.build();
- HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
-
- if (response.statusCode() != 200) {
- throw new IOException("HTTP " + response.statusCode());
- }
- return response.body();
- } catch (Exception e) {
- directFailCount++;
- throw e;
- }
- }
-
- private static String proxyGet(String endpoint) throws IOException {
- try {
- HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
- .uri(URI.create(BASE_URL + "/proxy" + endpoint))
- .timeout(Duration.ofSeconds(30))
- .header("User-Agent", "ZernMC-Launcher/1.0")
- .GET();
-
- // ===== ДОБАВИТЬ ТОКЕН АВТОРИЗАЦИИ =====
- String accessToken = AuthManager.getAccessToken();
- if (accessToken != null && !accessToken.equals("0")) {
- requestBuilder.header("Authorization", "Bearer " + accessToken);
- }
-
- HttpRequest request = requestBuilder.build();
- HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
-
- if (response.statusCode() != 200) {
- throw new IOException("HTTP " + response.statusCode());
- }
-
- proxySuccessCount++;
- return response.body();
- } catch (Exception e) {
- throw new IOException("Ошибка прокси: " + e.getMessage(), e);
- }
- }
-
- // ====================== МЕТОДЫ ДЛЯ EXTERNAL РЕСУРСОВ ======================
-
- public static List getFabricLoaderVersions() throws IOException, InterruptedException {
- String url = "https://meta.fabricmc.net/v2/versions/loader";
- return parseFabricVersionsFromJson(getWithSmartProxy(url));
- }
-
- public static JSONObject getMojangVersionManifest() throws IOException, InterruptedException {
- String url = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
- String response = getWithSmartProxy(url);
- return new JSONObject(response);
- }
-
- public static JSONObject getMojangVersionJson(String versionId) throws IOException, InterruptedException {
- JSONObject manifest = getMojangVersionManifest();
- JSONArray versions = manifest.getJSONArray("versions");
-
- for (int i = 0; i < versions.length(); i++) {
- JSONObject v = versions.getJSONObject(i);
- if (v.getString("id").equals(versionId)) {
- return new JSONObject(getWithSmartProxy(v.getString("url")));
- }
- }
- throw new IOException("Version " + versionId + " not found");
- }
-
- public static String getForgeVersionsXml() throws IOException, InterruptedException {
- String url = "https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml";
- return getWithSmartProxy(url);
- }
-
- public static void downloadFile(String url, Path target) throws Exception {
- downloadFileWithSmartProxy(url, target);
- }
-
- public static void downloadAsset(String hash, Path target) throws Exception {
- String url = "https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash;
- downloadFileWithSmartProxy(url, target);
- }
-
- public static String downloadString(String url) throws IOException, InterruptedException {
- return getWithSmartProxy(url);
- }
-
- private static List parseFabricVersionsFromJson(String json) {
- JSONArray array = new JSONArray(json);
- List versions = new ArrayList<>();
- for (int i = 0; i < array.length(); i++) {
- JSONObject obj = array.getJSONObject(i);
- if (obj.has("version")) {
- versions.add(obj.getString("version"));
- }
- }
- return versions;
- }
-
- // ====================== ВСПОМОГАТЕЛЬНЫЕ ======================
-
- public static String getLauncherVersionInfo() throws IOException, InterruptedException {
- return get("/launcher/version");
- }
-
- public static void forceProxyMode() {
- useProxyMode.set(true);
- System.out.println(ZAnsi.yellow("Принудительно включен глобальный прокси режим"));
- }
-
- public static void disableProxyMode() {
- useProxyMode.set(false);
- for (ServiceType type : ServiceType.values()) {
- if (!type.isAlwaysDirect()) {
- serviceProxyMode.put(type, false);
- serviceFailCount.put(type, 0);
- }
- }
- System.out.println(ZAnsi.green("Режим прокси выключен"));
- }
-
- public static boolean isProxyMode() {
- return useProxyMode.get();
- }
-
- public static void printStats() {
- System.out.println(ZAnsi.cyan("\n=== Статистика сети ==="));
- System.out.println(ZAnsi.white("Глобальный прокси: ") + (useProxyMode.get() ? "ВКЛ" : "ВЫКЛ"));
- System.out.println(ZAnsi.white("Прямых успехов: ") + directSuccessCount);
- System.out.println(ZAnsi.white("Прямых неудач: ") + directFailCount);
- System.out.println(ZAnsi.white("Прокси успехов: ") + proxySuccessCount);
-
- System.out.println(ZAnsi.cyan("\nСтатус сервисов:"));
- for (ServiceType type : ServiceType.values()) {
- if (type.isAlwaysDirect()) continue;
- String status = serviceProxyMode.get(type) ? ZAnsi.red("ПРОКСИ") : ZAnsi.green("ПРЯМО");
- String health = serviceHealthy.get(type) ? ZAnsi.green("[+]") : ZAnsi.red("[-]");
- System.out.println(ZAnsi.white(" " + type.name() + ": ") + status + " " + health);
- }
- }
-}
\ No newline at end of file
diff --git a/launcher/src/test/java/me/sashegdev/zernmc/launcher/auth/AuthManagerParsingTest.java b/launcher/src/test/java/me/sashegdev/zernmc/launcher/auth/AuthManagerParsingTest.java
deleted file mode 100644
index c585785..0000000
--- a/launcher/src/test/java/me/sashegdev/zernmc/launcher/auth/AuthManagerParsingTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package me.sashegdev.zernmc.launcher.auth;
-
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * Unit tests for AuthManager error extraction and response parsing.
- * Tests the contract between server error responses and Java client parsing.
- */
-class AuthManagerParsingTest {
-
- @Test
- void extractError_simpleStringDetail() {
- // Server: raise HTTPException(401, "Неверное имя пользователя или пароль")
- String body = "{\"detail\":\"Неверное имя пользователя или пароль\"}";
- String error = extractError(body);
- assertEquals("Неверное имя пользователя или пароль", error);
- }
-
- @Test
- void extractError_validationErrorArray() {
- // FastAPI 422: {"detail": [{"loc": ["body", "username"], "msg": "...", "type": "..."}]}
- String body = "{" +
- "\"detail\":[" +
- "{\"loc\":[\"body\",\"username\"],\"msg\":\"String should have at least 3 characters\",\"type\":\"string_too_short\"}" +
- "]" +
- "}";
- String error = extractError(body);
- assertEquals("String should have at least 3 characters", error);
- }
-
- @Test
- void extractError_multipleValidationErrors_returnsFirst() {
- String body = "{" +
- "\"detail\":[" +
- "{\"loc\":[\"body\",\"username\"],\"msg\":\"Username error\",\"type\":\"value_error\"}," +
- "{\"loc\":[\"body\",\"password\"],\"msg\":\"Password error\",\"type\":\"value_error\"}" +
- "]" +
- "}";
- String error = extractError(body);
- assertEquals("Username error", error);
- }
-
- @Test
- void extractError_plainTextBody() {
- // Non-JSON error body
- String body = "Internal Server Error";
- String error = extractError(body);
- assertEquals("Internal Server Error", error);
- }
-
- @Test
- void extractError_longBody_truncated() {
- String longBody = "A".repeat(300);
- String error = extractError(longBody);
- assertEquals(203, error.length()); // 200 + "..."
- assertTrue(error.endsWith("..."));
- }
-
- @Test
- void extractError_emptyDetail() {
- String body = "{\"detail\":\"\"}";
- String error = extractError(body);
- assertEquals("", error);
- }
-
- @Test
- void extractError_noDetailField_returnsBody() {
- String body = "{\"error\":\"something went wrong\"}";
- String error = extractError(body);
- assertEquals("{\"error\":\"something went wrong\"}", error);
- }
-
- /**
- * Replicates AuthManager.extractError() logic for testing.
- * If this passes, the real method in AuthManager works correctly.
- */
- private static String extractError(String body) {
- try {
- com.google.gson.JsonObject json = com.google.gson.JsonParser.parseString(body).getAsJsonObject();
- if (json.has("detail")) {
- if (json.get("detail").isJsonArray()) {
- return json.getAsJsonArray("detail").get(0).getAsJsonObject().get("msg").getAsString();
- }
- return json.get("detail").getAsString();
- }
- } catch (Exception ignored) {}
- return body.length() > 200 ? body.substring(0, 200) + "..." : body;
- }
-}
diff --git a/launcher/src/test/java/me/sashegdev/zernmc/launcher/integration/ServerIntegrationTest.java b/launcher/src/test/java/me/sashegdev/zernmc/launcher/integration/ServerIntegrationTest.java
deleted file mode 100644
index d5a0890..0000000
--- a/launcher/src/test/java/me/sashegdev/zernmc/launcher/integration/ServerIntegrationTest.java
+++ /dev/null
@@ -1,469 +0,0 @@
-package me.sashegdev.zernmc.launcher.integration;
-
-import org.junit.jupiter.api.*;
-import static org.junit.jupiter.api.Assertions.*;
-
-import java.io.*;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-
-import me.sashegdev.zernmc.launcher.utils.ZHttpClient;
-
-/**
- * Integration tests: real Java client ↔ real Python server.
- *
- * These tests:
- * 1. Start the FastAPI test server via Python subprocess
- * 2. Use actual Java HTTP client code to make requests
- * 3. Verify JSON parsing and response handling
- *
- * Requires: Python 3, pytest, and the server/.venv to be available.
- */
-@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
-class ServerIntegrationTest {
-
- private static Process serverProcess;
- private static String serverBaseUrl;
- private static Path testDir;
- private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
-
- @BeforeAll
- static void startTestServer() throws Exception {
- // Create temp directory for test data
- testDir = Files.createTempDirectory("zern_integration_test_");
-
- // Find the server directory
- String serverDir = findServerDir();
- if (serverDir == null) {
- System.out.println("WARNING: Server directory not found, skipping integration tests");
- serverBaseUrl = null;
- return;
- }
-
- // Start the test server on a random port
- int port = findFreePort();
- serverBaseUrl = "http://127.0.0.1:" + port;
-
- System.out.println("Starting test server on " + serverBaseUrl);
- System.out.println("Server directory: " + serverDir);
-
- // Find Python executable (prefer venv python)
- String pythonPath = findPythonPath(serverDir);
- if (pythonPath == null) {
- System.out.println("WARNING: Python not found, skipping integration tests");
- serverBaseUrl = null;
- return;
- }
-
- // Create a Python startup script that properly sets up paths
- String startupScript =
- "import sys, os, tempfile\n" +
- "from pathlib import Path\n" +
- "sys.path.insert(0, '" + serverDir + "')\n" +
- "os.chdir('" + serverDir + "')\n" +
- "import auth\n" +
- "db_dir = tempfile.mkdtemp()\n" +
- "auth.AUTH_DB = Path(db_dir) / 'auth.db'\n" +
- "auth.SECRET_KEY = Path(db_dir) / '.secret_key'\n" +
- "auth.init_db()\n" +
- "import uvicorn\n" +
- "import main\n" +
- "uvicorn.run(main.app, host='127.0.0.1', port=" + port + ", log_level='error')\n";
-
- ProcessBuilder pb = new ProcessBuilder(pythonPath, "-c", startupScript);
- pb.directory(new File(serverDir));
- pb.redirectErrorStream(true);
-
- try {
- serverProcess = pb.start();
- System.out.println("Server process started, PID: " + serverProcess.pid());
- } catch (IOException e) {
- System.out.println("WARNING: Could not start server process: " + e.getMessage());
- System.out.println("Skipping integration tests");
- serverBaseUrl = null;
- return;
- }
-
- // Wait for server to start
- Thread.sleep(4000);
-
- // Verify server is running
- try {
- URL url = new URL(serverBaseUrl + "/health");
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setConnectTimeout(5000);
- conn.connect();
- if (conn.getResponseCode() != 200) {
- System.out.println("WARNING: Server health check failed: " + conn.getResponseCode());
- System.out.println("Skipping integration tests");
- serverBaseUrl = null;
- if (serverProcess != null) serverProcess.destroy();
- conn.disconnect();
- return;
- }
- conn.disconnect();
- System.out.println("Test server started successfully");
- } catch (Exception e) {
- System.out.println("WARNING: Server failed to start: " + e.getMessage());
- System.out.println("Skipping integration tests");
- serverBaseUrl = null;
- if (serverProcess != null) serverProcess.destroy();
- }
- }
-
- @AfterAll
- static void stopTestServer() {
- if (serverProcess != null) {
- serverProcess.destroy();
- try {
- serverProcess.waitFor(5000, java.util.concurrent.TimeUnit.MILLISECONDS);
- } catch (InterruptedException ignored) {}
- }
- // Cleanup temp dir
- if (testDir != null) {
- try {
- Files.walk(testDir)
- .sorted(java.util.Comparator.reverseOrder())
- .forEach(path -> {
- try { Files.delete(path); } catch (IOException ignored) {}
- });
- } catch (IOException ignored) {}
- }
- }
-
- @BeforeEach
- void setUp() {
- if (serverBaseUrl != null) {
- ZHttpClient.setBaseUrl(serverBaseUrl);
- }
- }
-
- // ===== Auth flow tests =====
-
- @Test
- @Order(1)
- void testRegister() throws Exception {
- assumeServerRunning();
-
- String response = httpPost("/auth/register", "{" +
- "\"username\":\"integration_test_user\"," +
- "\"password\":\"IntegrationTest123\"" +
- "}");
-
- JsonObject json = JsonParser.parseString(response).getAsJsonObject();
- assertTrue(json.has("access_token"));
- assertTrue(json.has("refresh_token"));
- assertTrue(json.has("expires_in"));
- assertTrue(json.has("uuid"));
- assertEquals("integration_test_user", json.get("username").getAsString());
- assertTrue(json.has("role"));
- }
-
- @Test
- @Order(2)
- void testLogin() throws Exception {
- assumeServerRunning();
-
- String response = httpPost("/auth/login", "{" +
- "\"username\":\"integration_test_user\"," +
- "\"password\":\"IntegrationTest123\"" +
- "}");
-
- JsonObject json = JsonParser.parseString(response).getAsJsonObject();
- assertTrue(json.has("access_token"));
- assertTrue(json.has("refresh_token"));
- assertEquals("integration_test_user", json.get("username").getAsString());
- assertTrue(json.has("role"));
- assertTrue(json.has("uuid"));
- }
-
- @Test
- @Order(3)
- void testDuplicateRegistration() throws Exception {
- assumeServerRunning();
-
- try {
- httpPost("/auth/register", "{" +
- "\"username\":\"integration_test_user\"," +
- "\"password\":\"AnotherPassword123\"" +
- "}");
- fail("Should have thrown IOException for duplicate registration");
- } catch (IOException e) {
- assertTrue(e.getMessage().contains("409") || e.getMessage().contains("409"),
- "Expected 409 conflict, got: " + e.getMessage());
- }
- }
-
- @Test
- @Order(4)
- void testLoginWrongPassword() throws Exception {
- assumeServerRunning();
-
- try {
- httpPost("/auth/login", "{" +
- "\"username\":\"integration_test_user\"," +
- "\"password\":\"WrongPassword\"" +
- "}");
- fail("Should have thrown IOException for wrong password");
- } catch (IOException e) {
- assertTrue(e.getMessage().contains("401"),
- "Expected 401, got: " + e.getMessage());
- }
- }
-
- @Test
- @Order(5)
- void testGetAdminMe() throws Exception {
- assumeServerRunning();
-
- // Login to get token
- String loginResp = httpPost("/auth/login", "{" +
- "\"username\":\"integration_test_user\"," +
- "\"password\":\"IntegrationTest123\"" +
- "}");
- JsonObject loginJson = JsonParser.parseString(loginResp).getAsJsonObject();
- String token = loginJson.get("access_token").getAsString();
-
- // Get user info
- String response = httpGet("/admin/me", token);
- JsonObject json = JsonParser.parseString(response).getAsJsonObject();
-
- assertTrue(json.has("id"));
- assertEquals("integration_test_user", json.get("username").getAsString());
- assertTrue(json.has("uuid"));
- assertTrue(json.has("role"));
- assertTrue(json.has("role_name"));
- assertTrue(json.has("has_pass"));
- assertTrue(json.has("permissions"));
- }
-
- @Test
- @Order(6)
- void testValidateToken() throws Exception {
- assumeServerRunning();
-
- String loginResp = httpPost("/auth/login", "{" +
- "\"username\":\"integration_test_user\"," +
- "\"password\":\"IntegrationTest123\"" +
- "}");
- JsonObject loginJson = JsonParser.parseString(loginResp).getAsJsonObject();
- String token = loginJson.get("access_token").getAsString();
- String uuid = loginJson.get("uuid").getAsString();
-
- // Validate
- String response = httpPost("/auth/validate",
- "{\"username\":\"integration_test_user\",\"uuid\":\"" + uuid + "\"}",
- token);
- JsonObject json = JsonParser.parseString(response).getAsJsonObject();
-
- assertTrue(json.has("valid"));
- assertTrue(json.get("valid").getAsBoolean());
- assertEquals("integration_test_user", json.get("username").getAsString());
- }
-
- @Test
- @Order(7)
- void testRefreshToken() throws Exception {
- assumeServerRunning();
-
- String loginResp = httpPost("/auth/login", "{" +
- "\"username\":\"integration_test_user\"," +
- "\"password\":\"IntegrationTest123\"" +
- "}");
- JsonObject loginJson = JsonParser.parseString(loginResp).getAsJsonObject();
- String refreshToken = loginJson.get("refresh_token").getAsString();
-
- // Refresh
- String response = httpPost("/auth/refresh",
- "{\"refresh_token\":\"" + refreshToken + "\"}");
- JsonObject json = JsonParser.parseString(response).getAsJsonObject();
-
- assertTrue(json.has("access_token"));
- assertTrue(json.has("refresh_token"));
- assertTrue(json.has("expires_in"));
- assertEquals("integration_test_user", json.get("username").getAsString());
- }
-
- // ===== Pack endpoint tests =====
-
- @Test
- @Order(8)
- void testPacksNoAuth() throws Exception {
- assumeServerRunning();
-
- try {
- httpGet("/packs");
- fail("Should have thrown IOException for unauthenticated access");
- } catch (IOException e) {
- assertTrue(e.getMessage().contains("401") || e.getMessage().contains("403"));
- }
- }
-
- @Test
- @Order(9)
- void testPackManifestPublic() throws Exception {
- assumeServerRunning();
-
- // /pack/{name} is public
- try {
- String response = httpGet("/pack/nonexistent-pack");
- JsonObject json = JsonParser.parseString(response).getAsJsonObject();
- fail("Should have thrown IOException for non-existent pack");
- } catch (IOException e) {
- assertTrue(e.getMessage().contains("404"),
- "Expected 404, got: " + e.getMessage());
- }
- }
-
- @Test
- @Order(10)
- void testLauncherVersion() throws Exception {
- assumeServerRunning();
-
- String response = httpGet("/launcher/version");
- JsonObject json = JsonParser.parseString(response).getAsJsonObject();
- assertTrue(json.has("version") || json.has("latest"));
- }
-
- // ===== Helper methods =====
-
- private static void assumeServerRunning() {
- org.junit.jupiter.api.Assumptions.assumeTrue(
- serverBaseUrl != null && serverProcess != null && serverProcess.isAlive(),
- "Test server is not running"
- );
- }
-
- private static String httpPost(String endpoint, String body) throws IOException {
- return httpPost(endpoint, body, null);
- }
-
- private static String httpPost(String endpoint, String body, String token) throws IOException {
- URL url = new URL(serverBaseUrl + endpoint);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setRequestMethod("POST");
- conn.setRequestProperty("Content-Type", "application/json");
- conn.setRequestProperty("Accept", "application/json");
- if (token != null) {
- conn.setRequestProperty("Authorization", "Bearer " + token);
- }
- conn.setDoOutput(true);
- conn.setConnectTimeout(10000);
- conn.setReadTimeout(10000);
-
- byte[] input = body.getBytes(StandardCharsets.UTF_8);
- conn.setFixedLengthStreamingMode(input.length);
- try (var os = conn.getOutputStream()) {
- os.write(input);
- }
-
- int code = conn.getResponseCode();
- String response = readResponse(conn, code);
-
- if (code >= 400) {
- throw new IOException("HTTP " + code + ": " + response);
- }
-
- conn.disconnect();
- return response;
- }
-
- private static String httpGet(String endpoint) throws IOException {
- return httpGet(endpoint, null);
- }
-
- private static String httpGet(String endpoint, String token) throws IOException {
- URL url = new URL(serverBaseUrl + endpoint);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setRequestMethod("GET");
- conn.setRequestProperty("Accept", "application/json");
- if (token != null) {
- conn.setRequestProperty("Authorization", "Bearer " + token);
- }
- conn.setConnectTimeout(10000);
- conn.setReadTimeout(10000);
-
- int code = conn.getResponseCode();
- String response = readResponse(conn, code);
-
- if (code >= 400) {
- throw new IOException("HTTP " + code + ": " + response);
- }
-
- conn.disconnect();
- return response;
- }
-
- private static String readResponse(HttpURLConnection conn, int code) throws IOException {
- var is = (code >= 200 && code < 300) ? conn.getInputStream() : conn.getErrorStream();
- if (is == null) {
- return "";
- }
- try (var scanner = new java.util.Scanner(is, StandardCharsets.UTF_8.name())) {
- return scanner.useDelimiter("\\A").hasNext() ? scanner.next() : "";
- }
- }
-
- private static String findPythonPath(String serverDir) {
- String[] paths = {
- serverDir + "/.venv/bin/python3",
- serverDir + "/.venv/bin/python",
- "python3",
- "python"
- };
- for (String path : paths) {
- File f = new File(path);
- if (f.exists() && f.canExecute()) {
- return path;
- }
- // Try which command
- try {
- Process p = new ProcessBuilder(path, "--version").start();
- int exit = p.waitFor();
- if (exit == 0) return path;
- } catch (Exception ignored) {}
- }
- return null;
- }
-
- private static String findServerDir() {
- String[] paths = {
- "../server",
- "server",
- System.getenv("SERVER_DIR")
- };
- for (String path : paths) {
- if (path != null && new File(path).exists() && new File(path, "main.py").exists()) {
- return path;
- }
- }
- return null;
- }
-
- private static int findFreePort() throws IOException {
- try (java.net.ServerSocket socket = new java.net.ServerSocket(0)) {
- return socket.getLocalPort();
- }
- }
-
- private static String readProcessOutput() throws IOException {
- if (serverProcess == null) return "";
- try (BufferedReader reader = new BufferedReader(
- new InputStreamReader(serverProcess.getInputStream(), StandardCharsets.UTF_8))) {
- StringBuilder sb = new StringBuilder();
- String line;
- while ((line = reader.readLine()) != null) {
- sb.append(line).append("\n");
- }
- return sb.toString();
- }
- }
-}
diff --git a/launcher/src/test/java/me/sashegdev/zernmc/launcher/minecraft/PackDownloaderParsingTest.java b/launcher/src/test/java/me/sashegdev/zernmc/launcher/minecraft/PackDownloaderParsingTest.java
deleted file mode 100644
index 8f5c91c..0000000
--- a/launcher/src/test/java/me/sashegdev/zernmc/launcher/minecraft/PackDownloaderParsingTest.java
+++ /dev/null
@@ -1,287 +0,0 @@
-package me.sashegdev.zernmc.launcher.minecraft;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import org.junit.jupiter.api.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * Unit tests for PackDownloader JSON parsing.
- * Tests that the Java client correctly parses server JSON responses.
- */
-class PackDownloaderParsingTest {
-
- private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
-
- // ===== /packs response parsing =====
-
- @Test
- void parsePacksResponse_singlePack() {
- String body = "{" +
- "\"packs\":[" +
- "{" +
- "\"name\":\"test-modpack\"," +
- "\"version\":3," +
- "\"files_count\":15," +
- "\"updated_at\":\"2024-01-15T10:30:00\"," +
- "\"minecraft_version\":\"1.20.4\"," +
- "\"loader_type\":\"fabric\"," +
- "\"loader_version\":\"0.15.6\"" +
- "}" +
- "]" +
- "}";
-
- List packs = parsePacksResponse(body);
- assertEquals(1, packs.size());
-
- ServerPack pack = packs.get(0);
- assertEquals("test-modpack", pack.getName());
- assertEquals(3, pack.getVersion());
- assertEquals(15, pack.getFilesCount());
- assertEquals("1.20.4", pack.getMinecraftVersion());
- assertEquals("fabric", pack.getLoaderType());
- assertEquals("0.15.6", pack.getLoaderVersion());
- assertNotNull(pack.getUpdatedAt());
- }
-
- @Test
- void parsePacksResponse_multiplePacks() {
- String body = "{" +
- "\"packs\":[" +
- "{\"name\":\"survival\",\"version\":1,\"files_count\":5,\"minecraft_version\":\"1.20.1\",\"loader_type\":\"vanilla\",\"loader_version\":null,\"updated_at\":null}," +
- "{\"name\":\"pvp\",\"version\":10,\"files_count\":50,\"minecraft_version\":\"1.20.4\",\"loader_type\":\"fabric\",\"loader_version\":\"0.15.6\",\"updated_at\":\"2024-02-01T00:00:00\"}" +
- "]" +
- "}";
-
- List packs = parsePacksResponse(body);
- assertEquals(2, packs.size());
- assertEquals("survival", packs.get(0).getName());
- assertEquals("pvp", packs.get(1).getName());
- }
-
- @Test
- void parsePacksResponse_skipsErroredPacks() {
- String body = "{" +
- "\"packs\":[" +
- "{\"name\":\"good-pack\",\"version\":1,\"files_count\":1,\"minecraft_version\":\"1.20.1\",\"loader_type\":\"vanilla\",\"loader_version\":null,\"updated_at\":null}," +
- "{\"name\":\"bad-pack\",\"error\":\"scan failed\"}," +
- "{\"name\":\"not-scanned\",\"status\":\"not_scanned\"}" +
- "]" +
- "}";
-
- List packs = parsePacksResponse(body);
- assertEquals(1, packs.size());
- assertEquals("good-pack", packs.get(0).getName());
- }
-
- @Test
- void parsePacksResponse_missingFields_defaults() {
- String body = "{" +
- "\"packs\":[" +
- "{\"name\":\"minimal-pack\"}" +
- "]" +
- "}";
-
- List packs = parsePacksResponse(body);
- assertEquals(1, packs.size());
-
- ServerPack pack = packs.get(0);
- assertEquals("minimal-pack", pack.getName());
- assertEquals(0, pack.getVersion()); // default
- assertEquals("unknown", pack.getMinecraftVersion()); // default
- assertEquals("vanilla", pack.getLoaderType()); // default
- assertEquals("", pack.getLoaderVersion()); // default
- assertEquals(0, pack.getFilesCount()); // default
- assertNull(pack.getUpdatedAt()); // default
- }
-
- @Test
- void parsePacksResponse_emptyList() {
- String body = "{\"packs\":[]}";
- List packs = parsePacksResponse(body);
- assertTrue(packs.isEmpty());
- }
-
- // ===== PackManifest parsing =====
-
- @Test
- void parsePackManifest_withFiles() {
- String body = "{" +
- "\"pack_name\":\"my-pack\"," +
- "\"version\":5," +
- "\"minecraft_version\":\"1.20.4\"," +
- "\"loader_type\":\"fabric\"," +
- "\"loader_version\":\"0.15.6\"," +
- "\"asset_index\":\"1.20.4\"," +
- "\"files\":{" +
- "\"mods/sodium.jar\":{\"path\":\"mods/sodium.jar\",\"url\":\"/pack/my-pack/file/mods/sodium.jar\",\"size\":1024000,\"hash\":\"abc123\"}," +
- "\"mods/fabric-api.jar\":{\"path\":\"mods/fabric-api.jar\",\"url\":\"/pack/my-pack/file/mods/fabric-api.jar\",\"size\":2048000,\"hash\":\"def456\"}" +
- "}" +
- "}";
-
- PackDownloader.PackManifest manifest = gson.fromJson(body, PackDownloader.PackManifest.class);
-
- assertEquals("my-pack", manifest.getPackName());
- assertEquals(5, manifest.getVersion());
- assertEquals("1.20.4", manifest.getMinecraftVersion());
- assertEquals("fabric", manifest.getLoaderType());
- assertEquals("0.15.6", manifest.getLoaderVersion());
- assertEquals("1.20.4", manifest.getAssetIndex());
- assertFalse(manifest.isEmpty());
- assertEquals(2, manifest.getFiles().size());
- }
-
- @Test
- void parsePackManifest_nullAssetIndex_defaultsToMinecraftVersion() {
- String body = "{" +
- "\"pack_name\":\"no-asset\"," +
- "\"version\":1," +
- "\"minecraft_version\":\"1.19.4\"," +
- "\"loader_type\":\"vanilla\"," +
- "\"loader_version\":null" +
- "}";
-
- PackDownloader.PackManifest manifest = gson.fromJson(body, PackDownloader.PackManifest.class);
- assertEquals("1.19.4", manifest.getAssetIndex()); // defaults to minecraft_version
- }
-
- @Test
- void parsePackManifest_noFiles_isEmpty() {
- String body = "{" +
- "\"pack_name\":\"empty-pack\"," +
- "\"version\":1," +
- "\"minecraft_version\":\"1.20.1\"," +
- "\"loader_type\":\"vanilla\"," +
- "\"loader_version\":null" +
- "}";
-
- PackDownloader.PackManifest manifest = gson.fromJson(body, PackDownloader.PackManifest.class);
- assertTrue(manifest.isEmpty());
- }
-
- // ===== DiffResponse parsing =====
-
- @Test
- void parseDiffResponse_allFields() {
- String body = "{" +
- "\"version\":6," +
- "\"to_download\":[" +
- "{\"path\":\"mods/new-mod.jar\",\"url\":\"/pack/test/file/mods/new-mod.jar\",\"size\":512000,\"hash\":\"aaa111\"}" +
- "]," +
- "\"to_delete\":[\"mods/old-mod.jar\"]," +
- "\"to_update\":[\"mods/updated-mod.jar\"]" +
- "}";
-
- PackDownloader.DiffResponse diff = gson.fromJson(body, PackDownloader.DiffResponse.class);
-
- assertEquals(6, diff.getVersion());
- assertEquals(1, diff.getToDownload().size());
- assertEquals(1, diff.getToDelete().size());
- assertEquals(1, diff.getToUpdate().size());
-
- PackDownloader.FileInfo fileInfo = diff.getToDownload().get(0);
- assertEquals("mods/new-mod.jar", fileInfo.getPath());
- assertEquals("/pack/test/file/mods/new-mod.jar", fileInfo.getUrl());
- assertEquals(512000, fileInfo.getSize());
- assertEquals("aaa111", fileInfo.getHash());
- }
-
- @Test
- void parseDiffResponse_emptyArrays() {
- String body = "{" +
- "\"version\":1," +
- "\"to_download\":[]," +
- "\"to_delete\":[]," +
- "\"to_update\":[]" +
- "}";
-
- PackDownloader.DiffResponse diff = gson.fromJson(body, PackDownloader.DiffResponse.class);
- assertTrue(diff.getToDownload().isEmpty());
- assertTrue(diff.getToDelete().isEmpty());
- assertTrue(diff.getToUpdate().isEmpty());
- }
-
- @Test
- void parseDiffResponse_nullArrays_returnsEmpty() {
- String body = "{\"version\":1}";
-
- PackDownloader.DiffResponse diff = gson.fromJson(body, PackDownloader.DiffResponse.class);
- assertNotNull(diff.getToDownload());
- assertNotNull(diff.getToDelete());
- assertNotNull(diff.getToUpdate());
- assertTrue(diff.getToDownload().isEmpty());
- assertTrue(diff.getToDelete().isEmpty());
- }
-
- // ===== ServerPack toString =====
-
- @Test
- void serverPack_toString_withDate() {
- java.time.LocalDateTime date = java.time.LocalDateTime.of(2024, 3, 15, 12, 0);
- ServerPack pack = new ServerPack("my-pack", 2, "1.20.4", "fabric", "0.15.6", date, 25);
-
- String str = pack.toString();
- assertTrue(str.contains("my-pack"));
- assertTrue(str.contains("1.20.4"));
- assertTrue(str.contains("fabric"));
- assertTrue(str.contains("25 файлов"));
- assertTrue(str.contains("15.03.2024"));
- }
-
- @Test
- void serverPack_toString_withoutDate() {
- ServerPack pack = new ServerPack("my-pack", 2, "1.20.4", "fabric", "0.15.6", null, 25);
-
- String str = pack.toString();
- assertTrue(str.contains("my-pack"));
- assertTrue(str.contains("25 файлов"));
- assertFalse(str.contains("обновлен"));
- }
-
- // ===== Helper: replicates PackDownloader.parsePacksResponse() =====
-
- private static List parsePacksResponse(String responseBody) {
- JsonObject root = com.google.gson.JsonParser.parseString(responseBody).getAsJsonObject();
- JsonArray packsArray = root.getAsJsonArray("packs");
- List result = new ArrayList<>();
-
- for (var elem : packsArray) {
- JsonObject pack = elem.getAsJsonObject();
-
- if (pack.has("error") || (pack.has("status") && "not_scanned".equals(pack.get("status").getAsString()))) {
- continue;
- }
-
- try {
- String name = pack.get("name").getAsString();
- int version = pack.has("version") ? pack.get("version").getAsInt() : 0;
- String minecraftVersion = pack.has("minecraft_version") ? pack.get("minecraft_version").getAsString() : "unknown";
- String loaderType = pack.has("loader_type") ? pack.get("loader_type").getAsString() : "vanilla";
- String loaderVersion = pack.has("loader_version") && !pack.get("loader_version").isJsonNull()
- ? pack.get("loader_version").getAsString() : "";
- int filesCount = pack.has("files_count") ? pack.get("files_count").getAsInt() : 0;
-
- java.time.LocalDateTime updatedAt = null;
- if (pack.has("updated_at") && !pack.get("updated_at").isJsonNull()) {
- try {
- updatedAt = java.time.LocalDateTime.parse(pack.get("updated_at").getAsString(),
- java.time.format.DateTimeFormatter.ISO_DATE_TIME);
- } catch (Exception ignored) {}
- }
-
- result.add(new ServerPack(name, version, minecraftVersion, loaderType,
- loaderVersion, updatedAt, filesCount));
- } catch (Exception e) {
- System.err.println("Ошибка парсинга пака: " + e.getMessage());
- }
- }
-
- return result;
- }
-}