КЛИЕНТ ЛАУНЧЕРА ЙОООО
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
logs/
|
||||||
|
__pycache__/
|
||||||
|
./.venv/
|
||||||
|
launcher/target
|
||||||
|
server/builds
|
||||||
|
server/packs
|
||||||
|
server/data
|
||||||
|
jre
|
||||||
|
.vscode
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>me.sashegdev</groupId>
|
||||||
|
<artifactId>ZernMCLauncher</artifactId>
|
||||||
|
<version>1.0.2</version>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.5.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputFile>../server/builds/ZernMCLauncher.jar</outputFile>
|
||||||
|
<transformers>
|
||||||
|
<transformer>
|
||||||
|
<mainClass>${mainClass}</mainClass>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.akathist.maven.plugins.launch4j</groupId>
|
||||||
|
<artifactId>launch4j-maven-plugin</artifactId>
|
||||||
|
<version>2.5.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>l4j</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>launch4j</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outfile>../server/builds/ZernMCLauncher.exe</outfile>
|
||||||
|
<jar>../server/builds/ZernMCLauncher.jar</jar>
|
||||||
|
<headerType>console</headerType>
|
||||||
|
<dontWrapJar>false</dontWrapJar>
|
||||||
|
<jre>
|
||||||
|
<path>jre21</path>
|
||||||
|
<minVersion>21</minVersion>
|
||||||
|
</jre>
|
||||||
|
<versionInfo>
|
||||||
|
<fileVersion>1.0.0.0</fileVersion>
|
||||||
|
<txtFileVersion>1.0.0</txtFileVersion>
|
||||||
|
<fileDescription>ZernMC Launcher</fileDescription>
|
||||||
|
<productVersion>1.0.0.0</productVersion>
|
||||||
|
<txtProductVersion>1.0.0</txtProductVersion>
|
||||||
|
<productName>ZernMC Launcher</productName>
|
||||||
|
</versionInfo>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-antrun-plugin</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>run</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<target>
|
||||||
|
<echo>${project.version}</echo>
|
||||||
|
<copy>
|
||||||
|
<fileset />
|
||||||
|
</copy>
|
||||||
|
<zip />
|
||||||
|
</target>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<mainClass>me.sashegdev.zernmc.launcher.Main</mainClass>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>me.sashegdev</groupId>
|
||||||
|
<artifactId>ZernMCLauncher</artifactId>
|
||||||
|
<version>1.0.2</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<mainClass>me.sashegdev.zernmc.launcher.Main</mainClass>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- HTTP Client -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>4.5.14</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JSON Processing -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>2.15.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Gson (для Instance.java) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>2.10.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- org.json (для VersionInstaller, ZHttpClient и т.д.) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.json</groupId>
|
||||||
|
<artifactId>json</artifactId>
|
||||||
|
<version>20231013</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Terminal UI -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.fusesource.jansi</groupId>
|
||||||
|
<artifactId>jansi</artifactId>
|
||||||
|
<version>2.4.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Progress Bar -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.tongfei</groupId>
|
||||||
|
<artifactId>progressbar</artifactId>
|
||||||
|
<version>0.9.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Commons IO -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.15.1</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<!-- Shade Plugin для uber-jar -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.5.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputFile>../server/builds/ZernMCLauncher.jar</outputFile>
|
||||||
|
<transformers>
|
||||||
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
|
<mainClass>${mainClass}</mainClass>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Launch4j для создания .exe -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.akathist.maven.plugins.launch4j</groupId>
|
||||||
|
<artifactId>launch4j-maven-plugin</artifactId>
|
||||||
|
<version>2.5.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>l4j</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>launch4j</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outfile>../server/builds/ZernMCLauncher.exe</outfile>
|
||||||
|
<jar>../server/builds/ZernMCLauncher.jar</jar>
|
||||||
|
<headerType>console</headerType>
|
||||||
|
<dontWrapJar>false</dontWrapJar>
|
||||||
|
<jre>
|
||||||
|
<path>jre21</path>
|
||||||
|
<minVersion>21</minVersion>
|
||||||
|
</jre>
|
||||||
|
<versionInfo>
|
||||||
|
<fileVersion>1.0.0.0</fileVersion>
|
||||||
|
<txtFileVersion>1.0.0</txtFileVersion>
|
||||||
|
<fileDescription>ZernMC Launcher</fileDescription>
|
||||||
|
<productVersion>1.0.0.0</productVersion>
|
||||||
|
<txtProductVersion>1.0.0</txtProductVersion>
|
||||||
|
<productName>ZernMC Launcher</productName>
|
||||||
|
</versionInfo>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Antrun: копирование JRE и создание build.version -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-antrun-plugin</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals><goal>run</goal></goals>
|
||||||
|
<configuration>
|
||||||
|
<target>
|
||||||
|
<echo file="../server/builds/build.version">${project.version}</echo>
|
||||||
|
|
||||||
|
<!-- Копируем JRE -->
|
||||||
|
<copy todir="../server/builds/jre21" overwrite="true">
|
||||||
|
<fileset dir="${user.home}/launcher/jre"/>
|
||||||
|
</copy>
|
||||||
|
|
||||||
|
<!-- Создаём zip -->
|
||||||
|
<zip destfile="../server/builds/ZernMCLauncher-${project.version}.zip"
|
||||||
|
basedir="../server/builds"
|
||||||
|
includes="ZernMCLauncher.exe,ZernMCLauncher.jar,build.version,jre21/**"/>
|
||||||
|
</target>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
package me.sashegdev.zernmc.launcher;
|
||||||
|
|
||||||
|
import me.sashegdev.zernmc.launcher.menu.*;
|
||||||
|
import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
|
||||||
|
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();
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.setProperty("org.jline.terminal.disableDeprecatedProviderWarning", "true");
|
||||||
|
ZAnsi.install();
|
||||||
|
|
||||||
|
System.out.print("\033[H\033[2J");
|
||||||
|
System.out.println(ZAnsi.brightGreen("Добро пожаловать в ZernMC Launcher " + CURRENT_VERSION));
|
||||||
|
|
||||||
|
checkAndAutoUpdateLauncher();
|
||||||
|
|
||||||
|
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<Path> 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)"));
|
||||||
|
|
||||||
|
// Заменяем текущий jar
|
||||||
|
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 {
|
||||||
|
while (true) {
|
||||||
|
List<String> options = List.of(
|
||||||
|
"Запустить игру",
|
||||||
|
"Проверка обновлений",
|
||||||
|
"Настройки",
|
||||||
|
"Проверка подключения к серверам Zern",
|
||||||
|
"Выход"
|
||||||
|
);
|
||||||
|
|
||||||
|
ArrowMenu menu = new ArrowMenu("Главное меню", options);
|
||||||
|
int choice = menu.show();
|
||||||
|
|
||||||
|
if (choice == -1 || choice == 4) {
|
||||||
|
System.out.print("\033[H\033[2J");
|
||||||
|
System.out.println(ZAnsi.yellow("До свидания!"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (choice) {
|
||||||
|
case 0 -> new LaunchMenu().show();
|
||||||
|
case 1 -> new UpdateMenu().show();
|
||||||
|
case 2 -> new SettingsMenu().show();
|
||||||
|
case 3 -> new ServerCheckMenu().show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,348 @@
|
|||||||
|
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.MinecraftLib;
|
||||||
|
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.ConsoleUtils;
|
||||||
|
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 LaunchMenu {
|
||||||
|
|
||||||
|
public void show() throws Exception {
|
||||||
|
while (true) {
|
||||||
|
ConsoleUtils.clearScreen();
|
||||||
|
List<Instance> instances = InstanceManager.getAllInstances();
|
||||||
|
|
||||||
|
List<String> 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) break;
|
||||||
|
if (choice == options.size() - 1) break; // Назад
|
||||||
|
|
||||||
|
if (choice == instances.size()) {
|
||||||
|
installNewPack();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance selected = instances.get(choice);
|
||||||
|
manageInstance(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void installNewPack() throws IOException {
|
||||||
|
ConsoleUtils.clearScreen();
|
||||||
|
System.out.println(ZAnsi.cyan("Получение списка версий Minecraft..."));
|
||||||
|
|
||||||
|
try {
|
||||||
|
VersionInstaller versionInstaller = new VersionInstaller(null);
|
||||||
|
List<MinecraftVersion> allVersions = versionInstaller.getAvailableVersions();
|
||||||
|
|
||||||
|
List<String> 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<String> 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(mcVersion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String loaderType = selectedLoader.contains("Fabric") ? "fabric" : "forge";
|
||||||
|
|
||||||
|
String loaderVersion;
|
||||||
|
if (loaderType.equals("fabric")) {
|
||||||
|
loaderVersion = askFabricLoaderVersion();
|
||||||
|
} else {
|
||||||
|
loaderVersion = askForgeVersion(mcVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loaderVersion == null) return;
|
||||||
|
|
||||||
|
String packName = askPackName();
|
||||||
|
if (packName == null) return;
|
||||||
|
|
||||||
|
InstanceManager.createInstanceFolder(packName);
|
||||||
|
Instance newInstance = InstanceManager.getInstance(packName);
|
||||||
|
|
||||||
|
MinecraftLib lib = new MinecraftLib(newInstance);
|
||||||
|
|
||||||
|
boolean success = loaderType.equals("fabric")
|
||||||
|
? lib.installFabric(mcVersion, loaderVersion)
|
||||||
|
: lib.installForge(mcVersion, loaderVersion);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
System.out.println(ZAnsi.brightGreen("\nСборка '" + packName + "' успешно установлена!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(ZAnsi.brightRed("Ошибка: " + e.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleUtils.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== Вспомогательные методы ======================
|
||||||
|
|
||||||
|
private List<String> buildLoaderOptions(String mcVersion) {
|
||||||
|
List<String> options = new ArrayList<>();
|
||||||
|
|
||||||
|
if (isFabricSupported(mcVersion)) {
|
||||||
|
options.add("Fabric");
|
||||||
|
}
|
||||||
|
if (isForgeSupported(mcVersion)) {
|
||||||
|
options.add("Forge");
|
||||||
|
}
|
||||||
|
options.add("Vanilla");
|
||||||
|
options.add("Назад");
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isFabricSupported(String version) {
|
||||||
|
// Fabric стабильно работает с 1.14+
|
||||||
|
return version.matches("^1\\.(1[4-9]|[2-9]\\d).*");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isForgeSupported(String version) {
|
||||||
|
// Forge поддерживает примерно до 1.21.4 на текущий момент
|
||||||
|
// Для версий 1.22+ и экспериментальных — отключаем
|
||||||
|
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 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() : "")));
|
||||||
|
|
||||||
|
List<String> options = new ArrayList<>();
|
||||||
|
options.add("Запустить сборку");
|
||||||
|
options.add("Изменить версию лоадера");
|
||||||
|
options.add("Удалить сборку");
|
||||||
|
options.add("Назад");
|
||||||
|
|
||||||
|
ArrowMenu menu = new ArrowMenu("Действия", options);
|
||||||
|
int choice = menu.show();
|
||||||
|
|
||||||
|
if (choice == -1 || choice == 3) return; // Esc или Назад
|
||||||
|
|
||||||
|
switch (choice) {
|
||||||
|
case 0 -> launchExistingInstance(instance);
|
||||||
|
case 1 -> changeLoaderVersion(instance);
|
||||||
|
case 2 -> deleteInstance(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
success = lib.installForge(mcVersion, newLoaderVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
System.out.println(ZAnsi.brightGreen("Версия лоадера успешно изменена!"));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(ZAnsi.brightRed("Ошибка при смене лоадера: " + e.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleUtils.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteInstance(Instance instance) throws IOException {
|
||||||
|
System.out.println(ZAnsi.brightRed("Вы действительно хотите удалить сборку '" + instance.getName() + "'?"));
|
||||||
|
System.out.print(ZAnsi.white("Введите 'да' для подтверждения: "));
|
||||||
|
String confirm = new java.util.Scanner(System.in).nextLine().trim();
|
||||||
|
|
||||||
|
if ("да".equalsIgnoreCase(confirm)) {
|
||||||
|
InstanceManager.getInstance(instance.getName());
|
||||||
|
System.out.println(ZAnsi.brightGreen("Сборка удалена."));
|
||||||
|
} else {
|
||||||
|
System.out.println(ZAnsi.yellow("Отменено."));
|
||||||
|
}
|
||||||
|
ConsoleUtils.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String askFabricLoaderVersion() throws Exception {
|
||||||
|
System.out.println(ZAnsi.cyan("Получение списка версий Fabric Loader..."));
|
||||||
|
List<String> versions = ZHttpClient.getFabricLoaderVersions();
|
||||||
|
|
||||||
|
List<String> options = versions.stream()
|
||||||
|
.limit(30) // увеличил до 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 + "..."));
|
||||||
|
|
||||||
|
// Получаем все версии Forge из Maven
|
||||||
|
List<String> allForgeVersions = getAllForgeVersions();
|
||||||
|
|
||||||
|
// Фильтруем только те, которые подходят под нашу версию Minecraft
|
||||||
|
List<String> compatibleVersions = allForgeVersions.stream()
|
||||||
|
.filter(v -> v.startsWith(mcVersion + "-"))
|
||||||
|
.map(v -> v.substring(mcVersion.length() + 1)) // убираем "1.20.1-"
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (compatibleVersions.isEmpty()) {
|
||||||
|
System.out.println(ZAnsi.yellow("Не найдено совместимых версий Forge для " + mcVersion));
|
||||||
|
ConsoleUtils.pause();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> options = compatibleVersions.stream()
|
||||||
|
.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 String askPackName() {
|
||||||
|
System.out.print(ZAnsi.white("\nВведите название новой сборки: "));
|
||||||
|
String name = new java.util.Scanner(System.in).nextLine().trim();
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
System.out.println(ZAnsi.yellow("Отменено."));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createVanillaInstance(String mcVersion) throws Exception {
|
||||||
|
String packName = askPackName();
|
||||||
|
if (packName == null) 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("\nVanilla сборка '" + packName + "' успешно создана!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void launchExistingInstance(Instance instance) {
|
||||||
|
ConsoleUtils.clearScreen();
|
||||||
|
System.out.println(ZAnsi.brightGreen("Запуск сборки: " + instance.getName()));
|
||||||
|
|
||||||
|
MinecraftLib lib = new MinecraftLib(instance);
|
||||||
|
LaunchOptions options = new LaunchOptions();
|
||||||
|
|
||||||
|
try {
|
||||||
|
lib.launch(options);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(ZAnsi.brightRed("Ошибка при запуске: " + e.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleUtils.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getAllForgeVersions() throws Exception {
|
||||||
|
String metadataUrl = "https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml";
|
||||||
|
|
||||||
|
String xml = ZHttpClient.downloadString(metadataUrl); // добавь этот метод в ZHttpClient, если его нет
|
||||||
|
|
||||||
|
// Парсим простым способом (без XML парсера)
|
||||||
|
List<String> versions = new ArrayList<>();
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
while ((index = xml.indexOf("<version>", index)) != -1) {
|
||||||
|
int start = index + 9;
|
||||||
|
int end = xml.indexOf("</version>", 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
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 {
|
||||||
|
List<String> options = List.of(
|
||||||
|
"Проверить подключение к ZernMC серверу",
|
||||||
|
"Проверить доступ к Mojang (Minecraft)",
|
||||||
|
"Проверить доступ к Fabric Meta",
|
||||||
|
"Назад в главное меню"
|
||||||
|
);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleUtils.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkZernServer() {
|
||||||
|
System.out.println(ZAnsi.cyan("Проверка подключения к ZernMC серверу..."));
|
||||||
|
try {
|
||||||
|
String response = ZHttpClient.get("/health");
|
||||||
|
System.out.println(ZAnsi.brightGreen("Сервер успешно подключён!"));
|
||||||
|
System.out.println("Ответ: " + response);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(ZAnsi.brightRed("Не удалось подключиться к ZernMC серверу"));
|
||||||
|
System.out.println("Ошибка: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkMojang() {
|
||||||
|
System.out.println(ZAnsi.cyan("Проверка доступа к Mojang..."));
|
||||||
|
try {
|
||||||
|
HttpClient client = HttpClient.newBuilder()
|
||||||
|
.connectTimeout(Duration.ofSeconds(8))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
|
if (response.statusCode() == 200) {
|
||||||
|
System.out.println(ZAnsi.brightGreen("Mojang доступен"));
|
||||||
|
} else {
|
||||||
|
System.out.println(ZAnsi.brightRed("Mojang вернул код " + response.statusCode()));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(ZAnsi.brightRed("Нет доступа к Mojang"));
|
||||||
|
System.out.println("Ошибка: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkFabric() {
|
||||||
|
System.out.println(ZAnsi.cyan("Проверка доступа к Fabric Meta..."));
|
||||||
|
try {
|
||||||
|
HttpClient client = HttpClient.newBuilder()
|
||||||
|
.connectTimeout(Duration.ofSeconds(8))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create("https://meta.fabricmc.net/v2/versions"))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
|
if (response.statusCode() == 200) {
|
||||||
|
System.out.println(ZAnsi.brightGreen("Fabric Meta доступен"));
|
||||||
|
} else {
|
||||||
|
System.out.println(ZAnsi.brightRed("Fabric Meta вернул код " + response.statusCode()));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(ZAnsi.brightRed("Нет доступа к Fabric Meta"));
|
||||||
|
System.out.println("Ошибка: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
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<String> 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("В будущем здесь будет список предустановленных оптимизаций.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package me.sashegdev.zernmc.launcher.menu;
|
||||||
|
|
||||||
|
import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
|
||||||
|
import me.sashegdev.zernmc.launcher.utils.ConsoleUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class UpdateMenu {
|
||||||
|
|
||||||
|
public void show() throws IOException {
|
||||||
|
List<String> options = List.of(
|
||||||
|
"Проверить обновления сборки (модпака)",
|
||||||
|
"Проверить обновления лаунчера",
|
||||||
|
"Назад в главное меню"
|
||||||
|
);
|
||||||
|
|
||||||
|
ArrowMenu menu = new ArrowMenu("Проверка обновлений", options);
|
||||||
|
int choice = menu.show();
|
||||||
|
|
||||||
|
if (choice == -1 || choice == 2) return;
|
||||||
|
|
||||||
|
ConsoleUtils.clearScreen();
|
||||||
|
|
||||||
|
if (choice == 0) {
|
||||||
|
System.out.println("Проверка обновлений сборки...");
|
||||||
|
System.out.println("Дифф обновлений пока в заглушке (сборки ещё не загружены)");
|
||||||
|
System.out.println(" Эндпоинт: POST /pack/{pack_name}/diff");
|
||||||
|
} else {
|
||||||
|
System.out.println("Проверка обновлений лаунчера...");
|
||||||
|
System.out.println("Версия лаунчера актуальна (заглушка)");
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleUtils.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package me.sashegdev.zernmc.launcher.minecraft;
|
||||||
|
|
||||||
|
import me.sashegdev.zernmc.launcher.utils.ZAnsi;
|
||||||
|
|
||||||
|
public class Installer {
|
||||||
|
|
||||||
|
public static boolean installPack(String packName, Instance instance) {
|
||||||
|
System.out.println(ZAnsi.cyan("Начинается установка сборки: " + packName));
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// 1. Получить манифест пака (/pack/{packName})
|
||||||
|
// 2. Запустить diff
|
||||||
|
// 3. Скачать недостающие файлы
|
||||||
|
// 4. Установить Minecraft + Loader (через MinecraftLib)
|
||||||
|
|
||||||
|
System.out.println(ZAnsi.yellow("Установка пока в разработке..."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
public class Instance {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final Path path;
|
||||||
|
|
||||||
|
private String minecraftVersion;
|
||||||
|
private String loaderType; // vanilla, fabric, forge
|
||||||
|
private String loaderVersion;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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("]");
|
||||||
|
} 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;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// игнорируем, если файл повреждён
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveMetadata() {
|
||||||
|
Path metaFile = path.resolve("instance.json");
|
||||||
|
InstanceMeta meta = new InstanceMeta(minecraftVersion, loaderType, loaderVersion);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Files.writeString(metaFile, GSON.toJson(meta));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Внутренний класс для сериализации
|
||||||
|
private static class InstanceMeta {
|
||||||
|
String minecraftVersion;
|
||||||
|
String loaderType;
|
||||||
|
String loaderVersion;
|
||||||
|
|
||||||
|
public InstanceMeta(String minecraftVersion, String loaderType, String loaderVersion) {
|
||||||
|
this.minecraftVersion = minecraftVersion;
|
||||||
|
this.loaderType = loaderType;
|
||||||
|
this.loaderVersion = loaderVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
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<Instance> 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 createInstanceFolder(String name) throws IOException {
|
||||||
|
Path path = INSTANCES_DIR.resolve(name);
|
||||||
|
if (Files.exists(path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Files.createDirectories(path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
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.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.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Главный фасад MinecraftLib — точка входа для всей логики установки и запуска
|
||||||
|
*/
|
||||||
|
public class MinecraftLib {
|
||||||
|
|
||||||
|
private final Instance instance;
|
||||||
|
|
||||||
|
public MinecraftLib(Instance instance) {
|
||||||
|
this.instance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== УСТАНОВКА ======================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Установить vanilla версию Minecraft
|
||||||
|
*/
|
||||||
|
public boolean installMinecraft(String versionId) throws Exception {
|
||||||
|
VersionInstaller installer = new VersionInstaller(instance.getPath());
|
||||||
|
boolean success = installer.install(versionId);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
instance.setMinecraftVersion(versionId);
|
||||||
|
instance.setLoaderType("vanilla");
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean installForge(String minecraftVersion, String forgeVersion) throws Exception {
|
||||||
|
ForgeInstaller installer = new ForgeInstaller(instance);
|
||||||
|
return installer.install(minecraftVersion, forgeVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Установить Fabric для выбранной версии Minecraft
|
||||||
|
*/
|
||||||
|
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)) {
|
||||||
|
System.out.println(ZAnsi.yellow("Forge пока не поддерживается"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. В будущем здесь будет diff и скачивание модов
|
||||||
|
|
||||||
|
System.out.println(ZAnsi.brightGreen("Базовая установка сборки завершена!"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== ЗАПУСК ======================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сгенерировать команду запуска (пока заглушка)
|
||||||
|
*/
|
||||||
|
public List<String> buildLaunchCommand(LaunchOptions options) throws Exception {
|
||||||
|
System.out.println(ZAnsi.cyan("Генерация команды запуска для " + instance.getName() + "..."));
|
||||||
|
|
||||||
|
// TODO: Полная реализация LaunchCommandBuilder (перенос из MLL)
|
||||||
|
|
||||||
|
System.out.println(ZAnsi.yellow("Генерация команды запуска пока в разработке"));
|
||||||
|
return List.of("java", "-jar", "placeholder.jar", "--version", instance.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запустить сборку
|
||||||
|
*/
|
||||||
|
public void launch(LaunchOptions options) throws Exception {
|
||||||
|
System.out.println(ZAnsi.brightGreen("Запуск сборки: " + instance.getName()));
|
||||||
|
|
||||||
|
LaunchCommandBuilder builder = new LaunchCommandBuilder(instance);
|
||||||
|
List<String> command = builder.build(options);
|
||||||
|
|
||||||
|
System.out.println(ZAnsi.cyan("Команда запуска (" + command.size() + " аргументов):"));
|
||||||
|
for (String arg : command) {
|
||||||
|
System.out.println(" " + arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Реальный запуск ===
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(command);
|
||||||
|
pb.directory(instance.getPath().toFile());
|
||||||
|
|
||||||
|
// Важно: перенаправляем вывод Minecraft в консоль лаунчера
|
||||||
|
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(); // очищаем TUI перед запуском игры
|
||||||
|
|
||||||
|
Process process = pb.start();
|
||||||
|
|
||||||
|
// Ждём завершения игры
|
||||||
|
int exitCode = process.waitFor();
|
||||||
|
|
||||||
|
System.out.println(ZAnsi.yellow("\nMinecraft завершился с кодом: " + exitCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== ГЕТТЕРЫ ======================
|
||||||
|
|
||||||
|
public Instance getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
package me.sashegdev.zernmc.launcher.minecraft.installer;
|
||||||
|
|
||||||
|
import me.sashegdev.zernmc.launcher.minecraft.Instance;
|
||||||
|
import me.sashegdev.zernmc.launcher.utils.ProgressBar;
|
||||||
|
import me.sashegdev.zernmc.launcher.utils.ZAnsi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.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();
|
||||||
|
|
||||||
|
// Шаг 1: Установка vanilla версии (если ещё не установлена)
|
||||||
|
VersionInstaller versionInstaller = new VersionInstaller(instancePath);
|
||||||
|
boolean mcOk = versionInstaller.install(minecraftVersion);
|
||||||
|
if (!mcOk) {
|
||||||
|
System.out.println(ZAnsi.brightRed("Не удалось установить Minecraft " + minecraftVersion));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Шаг 2: Скачивание и запуск Fabric Installer
|
||||||
|
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");
|
||||||
|
|
||||||
|
ProgressBar.show("Скачивание Fabric Installer", 0, 100, "%");
|
||||||
|
downloadFile(installerUrl, installerJar);
|
||||||
|
ProgressBar.finish("Fabric Installer скачан");
|
||||||
|
|
||||||
|
// Шаг 3: Запуск Fabric Installer
|
||||||
|
System.out.println(ZAnsi.cyan("Запуск Fabric Installer..."));
|
||||||
|
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(
|
||||||
|
"java", "-jar", installerJar.toAbsolutePath().toString(),
|
||||||
|
"client",
|
||||||
|
"-dir", instancePath.toAbsolutePath().toString(),
|
||||||
|
"-mcversion", minecraftVersion,
|
||||||
|
"-loader", loaderVersion,
|
||||||
|
"-noprofile",
|
||||||
|
"-snapshot"
|
||||||
|
);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Шаг 4: Проверка, что Fabric версия появилась
|
||||||
|
String fabricVersionId = "fabric-loader-" + loaderVersion + "-" + minecraftVersion;
|
||||||
|
Path fabricVersionDir = instancePath.resolve("versions").resolve(fabricVersionId);
|
||||||
|
|
||||||
|
if (Files.exists(fabricVersionDir)) {
|
||||||
|
System.out.println(ZAnsi.brightGreen("Fabric успешно установлен!"));
|
||||||
|
System.out.println("Версия: " + fabricVersionId);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
System.out.println(ZAnsi.brightRed("Fabric Installer отработал, но версия не найдена."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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().startsWith("0."))
|
||||||
|
.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 {
|
||||||
|
String url = "https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml";
|
||||||
|
String xml = downloadString(url);
|
||||||
|
|
||||||
|
int start = xml.indexOf("<latest>") + 8;
|
||||||
|
int end = xml.indexOf("</latest>", start);
|
||||||
|
return xml.substring(start, end).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String downloadString(String url) throws Exception {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).GET().build();
|
||||||
|
HttpResponse<String> resp = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
if (resp.statusCode() != 200) throw new IOException("HTTP " + resp.statusCode());
|
||||||
|
return resp.body();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadFile(String url, Path target) throws Exception {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).GET().build();
|
||||||
|
httpClient.send(request, HttpResponse.BodyHandlers.ofFile(target));
|
||||||
|
}
|
||||||
|
}
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
package me.sashegdev.zernmc.launcher.minecraft.installer;
|
||||||
|
|
||||||
|
import me.sashegdev.zernmc.launcher.minecraft.Instance;
|
||||||
|
import me.sashegdev.zernmc.launcher.utils.ProgressBar;
|
||||||
|
import me.sashegdev.zernmc.launcher.utils.ZAnsi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.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;
|
||||||
|
|
||||||
|
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
|
||||||
|
System.out.println(ZAnsi.cyan("Установка базовой версии Minecraft " + mcVersion + "..."));
|
||||||
|
VersionInstaller vanillaInstaller = new VersionInstaller(instance.getPath());
|
||||||
|
boolean vanillaSuccess = vanillaInstaller.install(mcVersion);
|
||||||
|
|
||||||
|
if (!vanillaSuccess) {
|
||||||
|
System.out.println(ZAnsi.brightRed("Не удалось установить базовую версию Minecraft"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Шаг 2: Создаём launcher_profiles.json (критично для Forge)
|
||||||
|
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");
|
||||||
|
|
||||||
|
ProgressBar.show("Скачивание Forge Installer", 0, 100, "%");
|
||||||
|
downloadFile(installerUrl, installerJar);
|
||||||
|
ProgressBar.finish("Forge Installer скачан (" + ProgressBar.formatBytes(Files.size(installerJar)) + ")");
|
||||||
|
|
||||||
|
System.out.println(ZAnsi.cyan("Запуск Forge Installer..."));
|
||||||
|
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(
|
||||||
|
"java",
|
||||||
|
"-jar",
|
||||||
|
installerJar.toAbsolutePath().toString(),
|
||||||
|
"--installClient"
|
||||||
|
);
|
||||||
|
|
||||||
|
pb.directory(instance.getPath().toFile());
|
||||||
|
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||||
|
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||||
|
|
||||||
|
Process process = pb.start();
|
||||||
|
int exitCode = process.waitFor();
|
||||||
|
|
||||||
|
if (exitCode != 0) {
|
||||||
|
System.out.println(ZAnsi.brightRed("Forge Installer завершился с ошибкой (код " + exitCode + ")"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println(ZAnsi.brightGreen("Forge " + forgeVersion + " успешно установлен!"));
|
||||||
|
|
||||||
|
instance.setMinecraftVersion(mcVersion);
|
||||||
|
instance.setLoaderType("forge");
|
||||||
|
instance.setLoaderVersion(forgeVersion);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создаёт минимальный launcher_profiles.json — Forge без него отказывается работать
|
||||||
|
*/
|
||||||
|
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 downloadFile(String url, Path target) throws Exception {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(url))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<Path> response = httpClient.send(request, HttpResponse.BodyHandlers.ofFile(target));
|
||||||
|
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw new IOException("Не удалось скачать Forge installer (HTTP " + response.statusCode() + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+206
@@ -0,0 +1,206 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
public class VersionInstaller {
|
||||||
|
|
||||||
|
private final Path minecraftDir;
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
|
||||||
|
public VersionInstaller(Path minecraftDir) {
|
||||||
|
this.minecraftDir = minecraftDir;
|
||||||
|
this.httpClient = HttpClient.newBuilder()
|
||||||
|
.connectTimeout(Duration.ofSeconds(30))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAvailableVersions() оставляем как было (с исправлением времени)
|
||||||
|
|
||||||
|
public List<MinecraftVersion> 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<MinecraftVersion> 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 boolean 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"));
|
||||||
|
|
||||||
|
// Ассеты
|
||||||
|
if (versionData.has("assetIndex")) {
|
||||||
|
System.out.println(ZAnsi.cyan("Скачивание ассетов..."));
|
||||||
|
downloadAssets(versionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println(ZAnsi.brightGreen("\nMinecraft " + versionId + " полностью установлен!"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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++;
|
||||||
|
if (count % 20 == 0) ProgressBar.show("Библиотеки", count, total, "");
|
||||||
|
}
|
||||||
|
ProgressBar.finish("Библиотеки загружены");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadAssets(JSONObject versionData) throws Exception {
|
||||||
|
JSONObject assetIndexInfo = versionData.getJSONObject("assetIndex");
|
||||||
|
String indexUrl = assetIndexInfo.getString("url");
|
||||||
|
String indexId = versionData.getString("assets");
|
||||||
|
|
||||||
|
Path indexPath = minecraftDir.resolve("assets/indexes").resolve(indexId + ".json");
|
||||||
|
Files.createDirectories(indexPath.getParent());
|
||||||
|
downloadFile(indexUrl, indexPath, "asset index");
|
||||||
|
|
||||||
|
String assetsJson = new String(Files.readAllBytes(indexPath));
|
||||||
|
JSONObject objects = new JSONObject(assetsJson).getJSONObject("objects");
|
||||||
|
|
||||||
|
System.out.println(ZAnsi.cyan("Скачивание " + objects.length() + " объектов ассетов..."));
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
int failed = 0;
|
||||||
|
|
||||||
|
for (String hash : objects.keySet()) {
|
||||||
|
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());
|
||||||
|
|
||||||
|
try {
|
||||||
|
downloadFile(url, target, ""); // пустой label = ассет
|
||||||
|
count++;
|
||||||
|
} catch (Exception e) {
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressBar.show("Ассеты", count, objects.length(), "файлов");
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressBar.finish("Ассеты загружены (" + count + " успешно, " + failed + " пропущено)");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// === Вспомогательные методы ===
|
||||||
|
|
||||||
|
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<String> 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 + "..."));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(url))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<Path> response = httpClient.send(request, HttpResponse.BodyHandlers.ofFile(target));
|
||||||
|
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
// Для ассетов 404 — это нормально, просто пропускаем
|
||||||
|
if (label.isEmpty()) {
|
||||||
|
return; // тихий пропуск для ассетов
|
||||||
|
}
|
||||||
|
throw new IOException("HTTP " + response.statusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!label.isEmpty()) {
|
||||||
|
long size = Files.size(target);
|
||||||
|
ProgressBar.finish(label + " (" + ProgressBar.formatBytes(size) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (!label.isEmpty()) {
|
||||||
|
// Для важных файлов (client.jar, библиотеки, index) — ошибка
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
// Для ассетов — просто пропускаем молча
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+162
@@ -0,0 +1,162 @@
|
|||||||
|
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 java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Генерирует полную команду запуска Minecraft (Vanilla / Fabric / Forge)
|
||||||
|
*/
|
||||||
|
public class LaunchCommandBuilder {
|
||||||
|
|
||||||
|
private final Instance instance;
|
||||||
|
|
||||||
|
public LaunchCommandBuilder(Instance instance) {
|
||||||
|
this.instance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> build(LaunchOptions options) throws Exception {
|
||||||
|
System.out.println(ZAnsi.cyan("Генерация команды запуска для " + instance.getName() + "..."));
|
||||||
|
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
|
||||||
|
// 1. Путь к Java
|
||||||
|
String javaPath = getJavaPath();
|
||||||
|
command.add(javaPath);
|
||||||
|
|
||||||
|
// 2. JVM аргументы
|
||||||
|
command.addAll(getJvmArguments(options));
|
||||||
|
|
||||||
|
// 3. Natives
|
||||||
|
Path nativesDir = instance.getPath().resolve("natives");
|
||||||
|
command.add("-Djava.library.path=" + nativesDir.toAbsolutePath());
|
||||||
|
|
||||||
|
// 4. Classpath
|
||||||
|
String classpath = buildClasspath();
|
||||||
|
command.add("-cp");
|
||||||
|
command.add(classpath);
|
||||||
|
|
||||||
|
// 5. Главный класс
|
||||||
|
String mainClass = getMainClass();
|
||||||
|
command.add(mainClass);
|
||||||
|
|
||||||
|
// 6. Аргументы Minecraft
|
||||||
|
command.addAll(getMinecraftArguments(options));
|
||||||
|
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getJavaPath() {
|
||||||
|
// Пока берём системную java. Позже можно добавить выбор из ~/.zernmc/jre/
|
||||||
|
return "java";
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getJvmArguments(LaunchOptions options) {
|
||||||
|
List<String> jvmArgs = new ArrayList<>();
|
||||||
|
|
||||||
|
// Выделенная память
|
||||||
|
int ramMB = options.getMaxMemory() > 0 ? options.getMaxMemory() : 2048;
|
||||||
|
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");
|
||||||
|
|
||||||
|
// Дополнительные JVM аргументы из настроек пользователя
|
||||||
|
if (options.getExtraJvmArgs() != null && !options.getExtraJvmArgs().isEmpty()) {
|
||||||
|
jvmArgs.addAll(options.getExtraJvmArgs());
|
||||||
|
}
|
||||||
|
|
||||||
|
return jvmArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildClasspath() throws Exception {
|
||||||
|
List<String> paths = new ArrayList<>();
|
||||||
|
|
||||||
|
String versionId = getVersionId();
|
||||||
|
Path versionsDir = instance.getPath().resolve("versions");
|
||||||
|
|
||||||
|
// 1. Основной jar версии (fabric-loader-...-1.20.1.jar)
|
||||||
|
paths.add(versionsDir.resolve(versionId).resolve(versionId + ".jar").toAbsolutePath().toString());
|
||||||
|
|
||||||
|
// 2. Все библиотеки
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Для Windows используем ";" вместо ":"
|
||||||
|
String separator = System.getProperty("os.name").toLowerCase().contains("win") ? ";" : ":";
|
||||||
|
return String.join(separator, paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getMainClass() {
|
||||||
|
String loaderType = instance.getLoaderType().toLowerCase();
|
||||||
|
|
||||||
|
if ("fabric".equals(loaderType)) {
|
||||||
|
String loaderVer = instance.getLoaderVersion();
|
||||||
|
if (loaderVer != null && loaderVer.startsWith("0.9")) {
|
||||||
|
return "net.fabricmc.loader.impl.launch.knot.KnotClient";
|
||||||
|
} else {
|
||||||
|
// Для более новых версий Fabric (0.14+)
|
||||||
|
return "net.fabricmc.loader.impl.launch.knot.KnotClient";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("forge".equals(loaderType)) {
|
||||||
|
return "net.minecraftforge.client.loading.ClientModLoader";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "net.minecraft.client.main.Main"; // Vanilla
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getMinecraftArguments(LaunchOptions options) {
|
||||||
|
List<String> 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());
|
||||||
|
|
||||||
|
if (options.getUsername() != null) {
|
||||||
|
args.add("--username");
|
||||||
|
args.add(options.getUsername());
|
||||||
|
} else {
|
||||||
|
args.add("--username");
|
||||||
|
args.add("Player");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Можно добавить --width, --height, --server и т.д. позже
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getVersionId() {
|
||||||
|
if ("vanilla".equalsIgnoreCase(instance.getLoaderType())) {
|
||||||
|
return instance.getMinecraftVersion();
|
||||||
|
} else {
|
||||||
|
// Для Fabric/Forge версия выглядит как fabric-loader-... или forge-...
|
||||||
|
return instance.getMinecraftVersion() + "-" + instance.getLoaderType() + "-" + instance.getLoaderVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
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<String> extraJvmArgs = new ArrayList<>();
|
||||||
|
|
||||||
|
// Геттеры и сеттеры
|
||||||
|
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<String> getExtraJvmArgs() { return extraJvmArgs; }
|
||||||
|
public void setExtraJvmArgs(List<String> extraJvmArgs) { this.extraJvmArgs = extraJvmArgs; }
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
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 + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
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<String> options;
|
||||||
|
private int selected = 0;
|
||||||
|
private final Terminal terminal;
|
||||||
|
|
||||||
|
private static final int VISIBLE_ITEMS = 7; // сколько строк показывать в списке
|
||||||
|
|
||||||
|
public ArrowMenu(String title, List<String> 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 == 'Ц') { // Up
|
||||||
|
selected = (selected - 1 + options.size()) % options.size();
|
||||||
|
}
|
||||||
|
else if (key == 's' || key == 'S' || key == 'ы' || key == 'Ы') { // Down
|
||||||
|
selected = (selected + 1) % options.size();
|
||||||
|
}
|
||||||
|
else if (key == 13 || key == 10) { // Enter
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
else if (key == 27) { // Esc
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
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 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 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 String getRamInfo() {
|
||||||
|
long totalMB = Runtime.getRuntime().maxMemory() / (1024 * 1024);
|
||||||
|
return "Доступно RAM: " + totalMB + " MB | Рекомендуется: " + maxMemory + " MB";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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("────────────────────────────────────────────────────────────"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package me.sashegdev.zernmc.launcher.utils;
|
||||||
|
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
public class Input {
|
||||||
|
|
||||||
|
private static final Scanner scanner = new Scanner(System.in);
|
||||||
|
|
||||||
|
public static String readLine() {
|
||||||
|
return scanner.nextLine().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String readLine(String prompt) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
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 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";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package me.sashegdev.zernmc.launcher.utils;
|
||||||
|
|
||||||
|
public class Version {
|
||||||
|
|
||||||
|
public static String getCurrentVersion() {
|
||||||
|
String version = Version.class.getPackage().getImplementationVersion();
|
||||||
|
return (version != null && !version.isBlank()) ? version : "1.0.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Универсальное сравнение версий
|
||||||
|
* Возвращает true, если serverVersion новее currentVersion
|
||||||
|
*/
|
||||||
|
public static boolean isNewer(String current, String server) {
|
||||||
|
if (current == null || server == null) return false;
|
||||||
|
|
||||||
|
// Убираем -SNAPSHOT для сравнения
|
||||||
|
current = current.replace("-SNAPSHOT", "").trim();
|
||||||
|
server = server.replace("-SNAPSHOT", "").trim();
|
||||||
|
|
||||||
|
if (current.equals(server)) return false;
|
||||||
|
|
||||||
|
String[] currentParts = current.split("\\.");
|
||||||
|
String[] serverParts = server.split("\\.");
|
||||||
|
|
||||||
|
int maxLength = Math.max(currentParts.length, serverParts.length);
|
||||||
|
|
||||||
|
for (int i = 0; i < maxLength; i++) {
|
||||||
|
int c = i < currentParts.length ? Integer.parseInt(currentParts[i]) : 0;
|
||||||
|
int s = i < serverParts.length ? Integer.parseInt(serverParts[i]) : 0;
|
||||||
|
|
||||||
|
if (s > c) return true;
|
||||||
|
if (s < c) return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
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 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 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 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 selected(String text) {
|
||||||
|
return Ansi.ansi()
|
||||||
|
.bgBright(Ansi.Color.WHITE)
|
||||||
|
.fgBlack()
|
||||||
|
.a(" > " + text + " ")
|
||||||
|
.reset()
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package me.sashegdev.zernmc.launcher.utils;
|
||||||
|
|
||||||
|
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.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ZHttpClient {
|
||||||
|
|
||||||
|
private static final java.net.http.HttpClient client = java.net.http.HttpClient.newBuilder()
|
||||||
|
.connectTimeout(Duration.ofSeconds(10))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private static final String BASE_URL = "http://87.120.187.36:1582";
|
||||||
|
|
||||||
|
public static String get(String endpoint) throws IOException, InterruptedException {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(BASE_URL + endpoint))
|
||||||
|
.timeout(Duration.ofSeconds(15))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw new IOException("HTTP " + response.statusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.body();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getBaseUrl() {
|
||||||
|
return BASE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLauncherVersion() throws IOException, InterruptedException {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(BASE_URL + "/launcher/version"))
|
||||||
|
.timeout(Duration.ofSeconds(10))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw new IOException("HTTP " + response.statusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.body();
|
||||||
|
}
|
||||||
|
public static String getLauncherVersionInfo() throws IOException, InterruptedException {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(BASE_URL + "/launcher/version"))
|
||||||
|
.timeout(Duration.ofSeconds(10))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw new IOException("HTTP " + response.statusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.body();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Получить список всех доступных версий Fabric Loader
|
||||||
|
*/
|
||||||
|
public static List<String> getFabricLoaderVersions() throws IOException, InterruptedException {
|
||||||
|
String url = "https://meta.fabricmc.net/v2/versions/loader";
|
||||||
|
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(url))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw new IOException("HTTP " + response.statusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Парсим JSON массив
|
||||||
|
org.json.JSONArray array = new org.json.JSONArray(response.body());
|
||||||
|
List<String> versions = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < array.length(); i++) {
|
||||||
|
org.json.JSONObject obj = array.getJSONObject(i);
|
||||||
|
if (obj.has("version")) {
|
||||||
|
versions.add(obj.getString("version"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
versions.sort((a, b) -> {
|
||||||
|
// Правильная семантическая сортировка версий
|
||||||
|
String[] partsA = a.split("\\+")[0].split("\\.");
|
||||||
|
String[] partsB = b.split("\\+")[0].split("\\.");
|
||||||
|
for (int i = 0; i < Math.min(partsA.length, partsB.length); i++) {
|
||||||
|
int cmp = Integer.compare(Integer.parseInt(partsB[i]), Integer.parseInt(partsA[i]));
|
||||||
|
if (cmp != 0) return cmp;
|
||||||
|
}
|
||||||
|
return b.compareTo(a); // fallback
|
||||||
|
});
|
||||||
|
|
||||||
|
return versions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String downloadString(String url) throws IOException, InterruptedException {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(url))
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw new IOException("HTTP " + response.statusCode());
|
||||||
|
}
|
||||||
|
return response.body();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user