Both | БЛЯЯЯ ЗАГРУЗКА ПАКОВ С СЕРВЕРА СЮДААА

This commit is contained in:
Sashegdev
2026-04-06 00:32:36 +00:00
parent 4edbe7e910
commit 0b4af1353d
13 changed files with 1482 additions and 373 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>me.sashegdev</groupId>
<artifactId>ZernMCLauncher</artifactId>
<version>1.0.2</version>
<version>1.0.3</version>
<build>
<plugins>
<plugin>
+1 -1
View File
@@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>me.sashegdev</groupId>
<artifactId>ZernMCLauncher</artifactId>
<version>1.0.2</version>
<version>1.0.4</version>
<packaging>jar</packaging>
<properties>
@@ -3,6 +3,8 @@ 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.PackDownloader;
import me.sashegdev.zernmc.launcher.minecraft.ServerPack;
import me.sashegdev.zernmc.launcher.minecraft.installer.VersionInstaller;
import me.sashegdev.zernmc.launcher.minecraft.model.LaunchOptions;
import me.sashegdev.zernmc.launcher.minecraft.model.MinecraftVersion;
@@ -35,7 +37,7 @@ public class LaunchMenu {
int choice = menu.show();
if (choice == -1) break;
if (choice == options.size() - 1) break; // Назад
if (choice == options.size() - 1) break;
if (choice == instances.size()) {
installNewPack();
@@ -47,72 +49,219 @@ public class LaunchMenu {
}
}
private void installNewPack() throws IOException {
private void installNewPack() throws Exception {
ConsoleUtils.clearScreen();
List<String> options = List.of(
"Установить сборку с сервера ZernMC",
"Установить Vanilla Minecraft",
"Создать сборку вручную (Fabric/Forge)",
"Назад"
);
ArrowMenu menu = new ArrowMenu("Установка новой сборки", options);
int choice = menu.show();
if (choice == -1 || choice == 3) return;
switch (choice) {
case 0 -> {
try {
installServerPack();
} catch (Exception e) {
System.out.println(ZAnsi.brightRed("Ошибка: " + e.getMessage()));
e.printStackTrace();
ConsoleUtils.pause();
}
}
case 1 -> createVanillaInstance();
case 2 -> createCustomInstance();
}
}
private void installServerPack() throws Exception {
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.cyan("Получение списка доступных сборок с сервера..."));
PackDownloader tempDownloader = new PackDownloader(null);
List<ServerPack> availablePacks = tempDownloader.getAvailablePacks();
if (availablePacks.isEmpty()) {
System.out.println(ZAnsi.yellow("Нет доступных сборок на сервере."));
ConsoleUtils.pause();
return;
}
// Исправлено: убраны спецсимволы для Windows
List<String> options = availablePacks.stream()
.map(p -> String.format("%s [%s + %s v%d] - %d файлов",
p.getName(),
p.getMinecraftVersion(),
p.getLoaderType(),
p.getVersion(),
p.getFilesCount()))
.collect(Collectors.toList());
options.add("Назад");
ArrowMenu menu = new ArrowMenu("Выберите сборку для установки", options);
int choice = menu.show();
if (choice == -1 || choice == options.size() - 1) return;
ServerPack selected = availablePacks.get(choice);
// Запрашиваем имя для локальной сборки
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.header("Установка сборки: " + selected.getName()));
System.out.println(ZAnsi.white(" Minecraft: ") + selected.getMinecraftVersion());
System.out.println(ZAnsi.white(" Лоадер: ") + selected.getLoaderType() + " " + selected.getLoaderVersion());
System.out.println(ZAnsi.white(" Версия: v") + selected.getVersion());
System.out.println(ZAnsi.white(" Файлов: ") + selected.getFilesCount());
System.out.println();
System.out.print(ZAnsi.white("Введите название локальной сборки (Enter = использовать имя пака): "));
String localName = Input.readLine();
if (localName.isEmpty()) {
localName = selected.getName();
}
// Проверяем, существует ли уже такая сборка
if (InstanceManager.getInstance(localName) != null) {
System.out.println(ZAnsi.brightRed("Сборка с таким именем уже существует!"));
ConsoleUtils.pause();
return;
}
// Создаем инстанс
InstanceManager.createInstanceFolder(localName);
Instance newInstance = InstanceManager.getInstance(localName);
// Устанавливаем сборку
PackDownloader packDownloader = new PackDownloader(newInstance);
boolean success = packDownloader.installOrUpdatePack(selected.getName(), selected);
if (success) {
System.out.println(ZAnsi.brightGreen("\n[OK] Сборка '" + localName + "' успешно установлена!"));
} else {
System.out.println(ZAnsi.brightRed("\n[FAIL] Не удалось установить сборку."));
}
ConsoleUtils.pause();
}
private void createVanillaInstance() throws Exception {
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()));
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();
String packName = askPackName();
if (packName == null) return;
if (InstanceManager.getInstance(packName) != null) {
System.out.println(ZAnsi.brightRed("Сборка с таким именем уже существует!"));
ConsoleUtils.pause();
return;
}
InstanceManager.createInstanceFolder(packName);
Instance newInstance = InstanceManager.getInstance(packName);
MinecraftLib lib = new MinecraftLib(newInstance);
boolean success = lib.installMinecraft(mcVersion);
if (success) {
System.out.println(ZAnsi.brightGreen("\n[OK] Vanilla сборка '" + packName + "' успешно создана!"));
} else {
System.out.println(ZAnsi.brightRed("\n[FAIL] Не удалось создать сборку."));
}
ConsoleUtils.pause();
}
private void createCustomInstance() throws Exception {
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.cyan("Получение списка версий Minecraft..."));
VersionInstaller versionInstaller = new VersionInstaller(null);
List<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();
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;
if (InstanceManager.getInstance(packName) != null) {
System.out.println(ZAnsi.brightRed("Сборка с таким именем уже существует!"));
ConsoleUtils.pause();
return;
}
InstanceManager.createInstanceFolder(packName);
Instance newInstance = InstanceManager.getInstance(packName);
MinecraftLib lib = new MinecraftLib(newInstance);
boolean success = loaderType.equals("fabric")
? lib.installFabric(mcVersion, loaderVersion)
: lib.installForge(mcVersion, loaderVersion);
if (success) {
System.out.println(ZAnsi.brightGreen("\n[OK] Сборка '" + packName + "' успешно установлена!"));
} else {
System.out.println(ZAnsi.brightRed("\n[FAIL] Не удалось установить сборку."));
}
ConsoleUtils.pause();
}
@@ -120,7 +269,7 @@ public class LaunchMenu {
private List<String> buildLoaderOptions(String mcVersion) {
List<String> options = new ArrayList<>();
if (isFabricSupported(mcVersion)) {
options.add("Fabric");
}
@@ -129,25 +278,22 @@ public class LaunchMenu {
}
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();
@@ -155,70 +301,120 @@ public class LaunchMenu {
System.out.println(ZAnsi.white("Версия: " + instance.getMinecraftVersion()));
System.out.println(ZAnsi.white("Лоадер: " + instance.getLoaderType() +
(instance.getLoaderVersion() != null ? " " + instance.getLoaderVersion() : "")));
if (instance.isServerPack()) {
System.out.println(ZAnsi.green("Серверная сборка: v" + instance.getServerVersion()));
}
List<String> options = new ArrayList<>();
options.add("Запустить сборку");
if (instance.isServerPack()) {
options.add("Проверить обновления");
}
options.add("Изменить версию лоадера");
options.add("Удалить сборку");
options.add("Назад");
ArrowMenu menu = new ArrowMenu("Действия", options);
int choice = menu.show();
if (choice == -1 || choice == 3) return; // Esc или Назад
if (choice == -1 || choice == options.size() - 1) return;
switch (choice) {
case 0 -> launchExistingInstance(instance);
case 1 -> changeLoaderVersion(instance);
case 2 -> deleteInstance(instance);
case 1 -> {
if (instance.isServerPack()) {
checkAndUpdateServerPack(instance);
} else {
changeLoaderVersion(instance);
}
}
case 2 -> {
if (instance.isServerPack()) {
changeLoaderVersion(instance);
} else {
deleteInstance(instance);
}
}
case 3 -> deleteInstance(instance);
}
}
}
private void checkAndUpdateServerPack(Instance instance) throws Exception {
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.cyan("Проверка обновлений для " + instance.getName()));
PackDownloader downloader = new PackDownloader(instance);
boolean hasUpdate = downloader.checkForUpdates(instance.getServerPackName());
if (!hasUpdate) {
System.out.println(ZAnsi.green("Сборка актуальна (v" + instance.getServerVersion() + ")"));
ConsoleUtils.pause();
return;
}
System.out.println(ZAnsi.brightYellow("Доступно обновление!"));
if (Input.confirm("Обновить сборку")) {
boolean success = downloader.updatePack(instance.getServerPackName());
if (success) {
System.out.println(ZAnsi.brightGreen("Сборка успешно обновлена!"));
} else {
System.out.println(ZAnsi.brightRed("Не удалось обновить сборку."));
}
} else {
System.out.println(ZAnsi.yellow("Обновление отменено."));
}
ConsoleUtils.pause();
}
private void changeLoaderVersion(Instance instance) throws Exception {
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.cyan("Изменение версии лоадера для " + instance.getName()));
String currentLoader = instance.getLoaderType();
String mcVersion = instance.getMinecraftVersion();
if ("vanilla".equalsIgnoreCase(currentLoader)) {
System.out.println(ZAnsi.yellow("Это vanilla сборка. Нельзя изменить лоадер."));
ConsoleUtils.pause();
return;
}
String newLoaderVersion;
if ("fabric".equalsIgnoreCase(currentLoader)) {
newLoaderVersion = askFabricLoaderVersion();
} else {
newLoaderVersion = askForgeVersion(mcVersion);
}
if (newLoaderVersion == null) return;
System.out.println(ZAnsi.cyan("Переустановка лоадера " + currentLoader + " " + newLoaderVersion + "..."));
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("Версия лоадера успешно изменена!"));
} else {
System.out.println(ZAnsi.brightRed("Не удалось изменить версию лоадера."));
}
} catch (Exception e) {
System.out.println(ZAnsi.brightRed("Ошибка при смене лоадера: " + e.getMessage()));
}
ConsoleUtils.pause();
}
private void deleteInstance(Instance instance) throws IOException {
ConsoleUtils.clearScreen();
@@ -226,15 +422,15 @@ public class LaunchMenu {
"Да, удалить сборку",
"Нет, отменить"
);
ArrowMenu confirmMenu = new ArrowMenu(
"Вы действительно хотите удалить сборку '" + instance.getName() + "'?",
confirmOptions
);
int choice = confirmMenu.show();
if (choice == 0) { // "Да, удалить"
if (choice == 0) {
boolean deleted = InstanceManager.deleteInstance(instance.getName());
if (deleted) {
System.out.println(ZAnsi.brightGreen("Сборка '" + instance.getName() + "' успешно удалена."));
@@ -244,105 +440,89 @@ public class LaunchMenu {
} 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
.limit(30)
.map(v -> "Fabric Loader " + v)
.collect(Collectors.toList());
options.add("Назад");
ArrowMenu menu = new ArrowMenu("Выбор версии Fabric Loader", options);
int choice = menu.show();
if (choice == -1 || choice == options.size() - 1) return null;
return versions.get(choice);
}
private String askForgeVersion(String mcVersion) throws Exception {
System.out.println(ZAnsi.cyan("Получение списка версий Forge для " + mcVersion + "..."));
// Получаем все версии 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-"
.map(v -> v.substring(mcVersion.length() + 1))
.collect(Collectors.toList());
if (compatibleVersions.isEmpty()) {
System.out.println(ZAnsi.yellow("Не найдено совместимых версий Forge для " + mcVersion));
ConsoleUtils.pause();
return null;
}
List<String> options = compatibleVersions.stream()
.limit(30)
.map(v -> "Forge " + v)
.collect(Collectors.toList());
options.add("Назад");
ArrowMenu menu = new ArrowMenu("Выбор версии Forge для " + mcVersion, options);
int choice = menu.show();
if (choice == -1 || choice == options.size() - 1) return null;
return compatibleVersions.get(choice);
}
private String askPackName() {
System.out.print(ZAnsi.white("\nВведите название новой сборки: "));
String name = Input.readLine(); // используем наш Input
String name = Input.readLine();
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()));
e.printStackTrace();
}
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, если его нет
String xml = ZHttpClient.downloadString(metadataUrl);
// Парсим простым способом (без XML парсера)
List<String> versions = new ArrayList<>();
int index = 0;
@@ -350,15 +530,14 @@ public class LaunchMenu {
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;
}
}
@@ -1,10 +1,18 @@
package me.sashegdev.zernmc.launcher.menu;
import me.sashegdev.zernmc.launcher.minecraft.Instance;
import me.sashegdev.zernmc.launcher.minecraft.InstanceManager;
import me.sashegdev.zernmc.launcher.minecraft.PackDownloader;
import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
import me.sashegdev.zernmc.launcher.utils.ConsoleUtils;
import me.sashegdev.zernmc.launcher.utils.Input;
import me.sashegdev.zernmc.launcher.utils.ZAnsi;
import me.sashegdev.zernmc.launcher.utils.ZHttpClient;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class UpdateMenu {
@@ -23,14 +31,122 @@ public class UpdateMenu {
ConsoleUtils.clearScreen();
if (choice == 0) {
System.out.println("Проверка обновлений сборки...");
System.out.println("Дифф обновлений пока в заглушке (сборки ещё не загружены)");
System.out.println(" Эндпоинт: POST /pack/{pack_name}/diff");
try {
checkPackUpdates();
} catch (Exception e) {
System.out.println(ZAnsi.brightRed("Ошибка: " + e.getMessage()));
e.printStackTrace();
ConsoleUtils.pause();
}
} else {
System.out.println("Проверка обновлений лаунчера...");
System.out.println("Версия лаунчера актуальна (заглушка)");
checkLauncherUpdates();
}
}
private void checkPackUpdates() throws Exception {
System.out.println(ZAnsi.cyan("Проверка обновлений сборок..."));
List<Instance> instances = InstanceManager.getAllInstances();
List<Instance> serverInstances = instances.stream()
.filter(Instance::isServerPack)
.collect(Collectors.toList());
if (serverInstances.isEmpty()) {
System.out.println(ZAnsi.yellow("Нет сборок, установленных с сервера."));
ConsoleUtils.pause();
return;
}
System.out.println(ZAnsi.cyan("\nПроверка обновлений для серверных сборок:\n"));
boolean hasUpdates = false;
List<Instance> updatableInstances = new ArrayList<>();
for (Instance instance : serverInstances) {
PackDownloader downloader = new PackDownloader(instance);
try {
boolean hasUpdate = downloader.checkForUpdates(instance.getServerPackName());
if (hasUpdate) {
System.out.println(ZAnsi.yellow(instance.getName() + " - Есть обновление!"));
updatableInstances.add(instance);
hasUpdates = true;
} else {
System.out.println(ZAnsi.green(instance.getName() + " - Актуальна"));
}
} catch (Exception e) {
System.out.println(ZAnsi.red(instance.getName() + " - Ошибка проверки: " + e.getMessage()));
}
}
if (!hasUpdates) {
System.out.println(ZAnsi.green("\nВсе сборки актуальны!"));
ConsoleUtils.pause();
return;
}
// Предлагаем обновить каждую сборку отдельно
for (Instance instance : updatableInstances) {
System.out.println(ZAnsi.brightYellow("\nОбновить сборку '" + instance.getName() + "'?"));
if (Input.confirm("Обновить")) {
System.out.println(ZAnsi.cyan("Обновление " + instance.getName() + "..."));
PackDownloader downloader = new PackDownloader(instance);
try {
boolean success = downloader.updatePack(instance.getServerPackName());
if (success) {
System.out.println(ZAnsi.brightGreen(instance.getName() + " обновлен"));
} else {
System.out.println(ZAnsi.brightRed(instance.getName() + " не удалось обновить"));
}
} catch (Exception e) {
System.out.println(ZAnsi.brightRed(instance.getName() + ": " + e.getMessage()));
}
} else {
System.out.println(ZAnsi.yellow(" Пропущено: " + instance.getName()));
}
}
ConsoleUtils.pause();
}
private void checkLauncherUpdates() {
System.out.println(ZAnsi.cyan("Проверка обновлений лаунчера..."));
try {
String json = ZHttpClient.getLauncherVersionInfo();
String serverVersion = extractVersion(json);
String currentVersion = me.sashegdev.zernmc.launcher.utils.Version.getCurrentVersion();
System.out.println(ZAnsi.white("Текущая версия: ") + currentVersion);
System.out.println(ZAnsi.white("Версия на сервере: ") + serverVersion);
if (me.sashegdev.zernmc.launcher.utils.Version.isNewer(currentVersion, serverVersion)) {
System.out.println(ZAnsi.brightYellow("\nДоступна новая версия!"));
if (Input.confirm("Обновить лаунчер?")) {
// Обновление будет при следующем запуске
System.out.println(ZAnsi.green("Лаунчер будет обновлен при следующем запуске."));
}
} else {
System.out.println(ZAnsi.brightGreen("Лаунчер актуален."));
}
} catch (Exception e) {
System.out.println(ZAnsi.yellow("Не удалось проверить обновления лаунчера."));
System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
}
ConsoleUtils.pause();
}
private String extractVersion(String json) {
try {
int start = json.indexOf("\"version\"");
if (start == -1) return "unknown";
start = json.indexOf("\"", start + 9) + 1;
int end = json.indexOf("\"", start);
return json.substring(start, end);
} catch (Exception e) {
return "unknown";
}
}
}
@@ -1,19 +0,0 @@
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;
}
}
@@ -8,7 +8,7 @@ import java.nio.file.Path;
// ЭТОТ КЛАСС РАБОТАЕТ НЕ ТРОГАТЬ ТОТ КТО БУДЕТ ЧИТАТЬ
// ЭТОТ КЛАСС РАБОТАЕТ НЕ ТРОГАТЬ ТОТ КТО БУДЕТ ЧИТАТЬ (на момент 1.0.2)
public class Instance {
private final String name;
private final Path path;
@@ -17,6 +17,9 @@ public class Instance {
private String loaderType; // vanilla, fabric, forge
private String loaderVersion;
private String assetIndex;
private boolean isServerPack; // флаг, что это сборка с сервера
private int serverVersion; // версия сборки на сервере
private String serverPackName; // имя пака на сервере
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
@@ -57,6 +60,34 @@ public class Instance {
saveMetadata();
}
public boolean isServerPack() {
return isServerPack;
}
public void setServerPack(boolean serverPack) {
this.isServerPack = serverPack;
saveMetadata();
}
public int getServerVersion() {
return serverVersion;
}
public void setServerVersion(int serverVersion) {
this.serverVersion = serverVersion;
saveMetadata();
}
public String getServerPackName() {
return serverPackName;
}
public void setServerPackName(String serverPackName) {
this.serverPackName = serverPackName;
saveMetadata();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(name);
@@ -67,6 +98,9 @@ public class Instance {
if (loaderVersion != null) sb.append(" ").append(loaderVersion);
}
sb.append("]");
if (isServerPack) {
sb.append("v").append(serverVersion);
}
} else {
sb.append(" [?]");
}
@@ -84,12 +118,18 @@ public class Instance {
this.loaderType = meta.loaderType;
this.loaderVersion = meta.loaderVersion;
this.assetIndex = meta.assetIndex;
this.isServerPack = meta.isServerPack;
this.serverVersion = meta.serverVersion;
this.serverPackName = meta.serverPackName;
} catch (Exception ignored) {}
}
private void saveMetadata() {
Path metaFile = path.resolve("instance.json");
InstanceMeta meta = new InstanceMeta(minecraftVersion, loaderType, loaderVersion, assetIndex);
InstanceMeta meta = new InstanceMeta(
minecraftVersion, loaderType, loaderVersion, assetIndex,
isServerPack, serverVersion, serverPackName
);
try {
Files.writeString(metaFile, GSON.toJson(meta));
} catch (IOException e) {
@@ -102,13 +142,22 @@ public class Instance {
String loaderType;
String loaderVersion;
String assetIndex;
boolean isServerPack = false;
int serverVersion = 0;
String serverPackName;
public InstanceMeta(String minecraftVersion, String loaderType,
String loaderVersion, String assetIndex) {
String loaderVersion, String assetIndex,
boolean isServerPack, int serverVersion,
String serverPackName) {
this.minecraftVersion = minecraftVersion;
this.loaderType = loaderType;
this.loaderVersion = loaderVersion;
this.assetIndex = assetIndex;
this.isServerPack = isServerPack;
this.serverVersion = serverVersion;
this.serverPackName = serverPackName;
}
}
}
@@ -0,0 +1,544 @@
package me.sashegdev.zernmc.launcher.minecraft;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import me.sashegdev.zernmc.launcher.utils.ProgressBar;
import me.sashegdev.zernmc.launcher.utils.ZAnsi;
import me.sashegdev.zernmc.launcher.utils.ZHttpClient;
import java.io.*;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
public class PackDownloader {
private final Instance instance;
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
private final HttpClient httpClient = HttpClient.newHttpClient();
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
public PackDownloader(Instance instance) {
this.instance = instance;
}
/**
* Получить список доступных паков с сервера
*/
public List<ServerPack> getAvailablePacks() throws Exception {
String response = ZHttpClient.get("/packs");
// Для отладки - выведем ответ сервера
System.out.println(ZAnsi.cyan("Ответ сервера: " + response));
JsonObject root = JsonParser.parseString(response).getAsJsonObject();
// Проверяем, есть ли поле "packs"
if (!root.has("packs")) {
System.out.println(ZAnsi.yellow("Сервер вернул неожиданный формат ответа"));
return new ArrayList<>();
}
JsonArray packsArray = root.getAsJsonArray("packs");
List<ServerPack> result = new ArrayList<>();
for (JsonElement elem : packsArray) {
JsonObject pack = elem.getAsJsonObject();
// Пропускаем паки с ошибками
if (pack.has("error")) {
System.out.println(ZAnsi.yellow("Пак имеет ошибку: " + pack.get("error").getAsString()));
continue;
}
// Пропускаем паки со статусом not_scanned
if (pack.has("status") && "not_scanned".equals(pack.get("status").getAsString())) {
System.out.println(ZAnsi.yellow("Пак " + pack.get("name").getAsString() + " не отсканирован на сервере"));
continue;
}
try {
// Пробуем получить name или pack_name (разные форматы)
String name = null;
if (pack.has("name")) {
name = pack.get("name").getAsString();
} else if (pack.has("pack_name")) {
name = pack.get("pack_name").getAsString();
} else {
continue; // Пропускаем если нет имени
}
int version = pack.has("version") ? pack.get("version").getAsInt() : 0;
// Получаем остальные поля (могут отсутствовать)
String minecraftVersion = pack.has("minecraft_version") ? pack.get("minecraft_version").getAsString() : "unknown";
String loaderType = pack.has("loader_type") ? pack.get("loader_type").getAsString() : "vanilla";
String loaderVersion = pack.has("loader_version") && !pack.get("loader_version").isJsonNull() ? pack.get("loader_version").getAsString() : "";
int filesCount = pack.has("files_count") ? pack.get("files_count").getAsInt() : 0;
// Парсим дату, если есть
LocalDateTime updatedAt = null;
if (pack.has("updated_at") && !pack.get("updated_at").isJsonNull()) {
try {
updatedAt = parseDateTime(pack.get("updated_at").getAsString());
} catch (Exception e) {
// Игнорируем ошибки парсинга даты
}
}
result.add(new ServerPack(name, version, minecraftVersion,
loaderType, loaderVersion, updatedAt, filesCount));
} catch (Exception e) {
System.err.println(ZAnsi.yellow("Ошибка парсинга пака: " + e.getMessage()));
}
}
return result;
}
/**
* Получить манифест пака
*/
public PackManifest getPackManifest(String packName) throws Exception {
String response = ZHttpClient.get("/pack/" + packName);
return gson.fromJson(response, PackManifest.class);
}
/**
* Установить или обновить сборку с сервера
*/
public boolean installOrUpdatePack(String packName, ServerPack serverPack) throws Exception {
System.out.println(ZAnsi.cyan("Установка сборки " + packName + " с сервера..."));
// 1. Получаем манифест
PackManifest manifest = getPackManifest(packName);
// 2. Сначала устанавливаем Minecraft + Loader через MinecraftLib
MinecraftLib lib = new MinecraftLib(instance);
System.out.println(ZAnsi.cyan("Установка Minecraft " + manifest.getMinecraftVersion() + "..."));
boolean needsMinecraftInstall = instance.getMinecraftVersion() == null ||
!instance.getMinecraftVersion().equals(manifest.getMinecraftVersion());
if (needsMinecraftInstall) {
if ("fabric".equalsIgnoreCase(manifest.getLoaderType())) {
boolean success = lib.installFabric(manifest.getMinecraftVersion(), manifest.getLoaderVersion());
if (!success) {
System.err.println(ZAnsi.brightRed("Не удалось установить Fabric"));
return false;
}
} else if ("forge".equalsIgnoreCase(manifest.getLoaderType())) {
boolean success = lib.installForge(manifest.getMinecraftVersion(), manifest.getLoaderVersion());
if (!success) {
System.err.println(ZAnsi.brightRed("Не удалось установить Forge"));
return false;
}
} else {
boolean success = lib.installMinecraft(manifest.getMinecraftVersion());
if (!success) {
System.err.println(ZAnsi.brightRed("Не удалось установить Vanilla Minecraft"));
return false;
}
}
} else {
System.out.println(ZAnsi.green("Minecraft уже установлен, пропускаем..."));
}
// 3. Сканируем локальные файлы ТОЛЬКО если есть файлы для скачивания
Map<String, String> localFiles = scanLocalFiles();
// Если в сборке нет файлов (только vanilla/loader), пропускаем diff
if (manifest.files == null || manifest.files.isEmpty()) {
System.out.println(ZAnsi.green("Сборка не содержит дополнительных файлов"));
// Обновляем метаданные инстанса
instance.setServerPack(true);
instance.setServerPackName(packName);
instance.setServerVersion(manifest.getVersion());
instance.setMinecraftVersion(manifest.getMinecraftVersion());
instance.setLoaderType(manifest.getLoaderType());
instance.setLoaderVersion(manifest.getLoaderVersion());
instance.setAssetIndex(manifest.getAssetIndex());
System.out.println(ZAnsi.brightGreen("Сборка успешно установлена!"));
return true;
}
// 4. Отправляем diff запрос
System.out.println(ZAnsi.cyan("Проверка файлов сборки..."));
DiffResponse diff = getDiff(packName, localFiles);
// 5. Применяем изменения
boolean success = applyDiff(diff, packName);
if (success) {
// 6. Обновляем метаданные инстанса
instance.setServerPack(true);
instance.setServerPackName(packName);
instance.setServerVersion(manifest.getVersion());
instance.setMinecraftVersion(manifest.getMinecraftVersion());
instance.setLoaderType(manifest.getLoaderType());
instance.setLoaderVersion(manifest.getLoaderVersion());
instance.setAssetIndex(manifest.getAssetIndex());
System.out.println(ZAnsi.brightGreen("Сборка успешно установлена!"));
}
return success;
}
/**
* Проверить наличие обновлений для серверной сборки
*/
public boolean checkForUpdates(String packName) throws Exception {
if (!instance.isServerPack()) return false;
PackManifest manifest = getPackManifest(packName);
int serverVersion = manifest.getVersion();
int localVersion = instance.getServerVersion();
return serverVersion > localVersion;
}
/**
* Обновить существующую серверную сборку
*/
public boolean updatePack(String packName) throws Exception {
System.out.println(ZAnsi.cyan("Проверка обновлений для " + instance.getName() + "..."));
PackManifest manifest = getPackManifest(packName);
int serverVersion = manifest.getVersion();
if (serverVersion <= instance.getServerVersion()) {
System.out.println(ZAnsi.green("Сборка уже актуальна (v" + instance.getServerVersion() + ")"));
return true;
}
System.out.println(ZAnsi.yellow("Доступно обновление: v" + instance.getServerVersion() + " → v" + serverVersion));
// Сканируем локальные файлы
Map<String, String> localFiles = scanLocalFiles();
// Получаем diff
DiffResponse diff = getDiff(packName, localFiles);
// Применяем изменения
boolean success = applyDiff(diff, packName);
if (success) {
instance.setServerVersion(serverVersion);
System.out.println(ZAnsi.brightGreen("Сборка обновлена до v" + serverVersion));
}
return success;
}
/**
* Сканирование локальных файлов и вычисление хешей
*/
private Map<String, String> scanLocalFiles() throws IOException {
Map<String, String> files = new HashMap<>();
Path instancePath = instance.getPath();
// Игнорируемые директории
Set<String> ignoredDirs = Set.of(
"resourcepacks", "shaderpacks", "saves", "logs",
"crash-reports", "screenshots", "journeymap", "config",
"natives", "assets", "libraries", "versions", "cache"
);
if (!Files.exists(instancePath)) {
return files;
}
Files.walk(instancePath)
.filter(Files::isRegularFile)
.forEach(file -> {
Path relative = instancePath.relativize(file);
String path = relative.toString().replace("\\", "/");
// Проверяем, не в игнорируемой ли директории
for (String ignored : ignoredDirs) {
if (path.startsWith(ignored + "/") || path.startsWith(ignored + "\\")) {
return;
}
}
try {
String hash = calculateHash(file);
files.put(path, hash);
} catch (Exception e) {
// Пропускаем файлы, которые не можем прочитать
}
});
return files;
}
/**
* Отправить diff запрос на сервер
*/
private DiffResponse getDiff(String packName, Map<String, String> localFiles) throws Exception {
String json = gson.toJson(localFiles);
System.out.println(ZAnsi.cyan("Отправка diff запроса для " + packName));
System.out.println(ZAnsi.cyan("JSON размер: " + json.length() + " байт"));
System.out.println(ZAnsi.cyan("JSON тело: " + json));
String baseUrl = ZHttpClient.getBaseUrl();
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
}
String url = baseUrl + "/pack/" + packName + "/diff";
System.out.println(ZAnsi.cyan("URL: " + url));
// ПРОБЛЕМА: стандартный HttpClient может отправлять chunked encoding
// РЕШЕНИЕ: используем HttpURLConnection вместо HttpClient
java.net.HttpURLConnection connection = null;
try {
java.net.URL urlObj = new java.net.URL(url);
connection = (java.net.HttpURLConnection) urlObj.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("Content-Length", String.valueOf(json.getBytes("UTF-8").length));
connection.setDoOutput(true);
connection.setConnectTimeout(30000);
connection.setReadTimeout(30000);
// Отправляем JSON
try (java.io.OutputStream os = connection.getOutputStream()) {
byte[] input = json.getBytes("UTF-8");
os.write(input, 0, input.length);
os.flush();
}
int responseCode = connection.getResponseCode();
System.out.println(ZAnsi.cyan("Diff ответ: HTTP " + responseCode));
// Читаем ответ
StringBuilder response = new StringBuilder();
try (java.io.InputStream is = responseCode < 400 ? connection.getInputStream() : connection.getErrorStream();
java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(is, "UTF-8"))) {
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
}
String responseBody = response.toString();
System.out.println(ZAnsi.cyan("Тело ответа: " + responseBody));
if (responseCode != 200) {
throw new IOException("HTTP " + responseCode + ": " + responseBody);
}
return gson.fromJson(responseBody, DiffResponse.class);
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
/**
* Применить diff (скачать новые файлы, удалить старые)
*/
private boolean applyDiff(DiffResponse diff, String packName) {
System.out.println(ZAnsi.cyan("\nПрименение изменений:"));
System.out.println(" Загрузить: " + diff.getToDownload().size() + " файлов");
System.out.println(" Удалить: " + diff.getToDelete().size() + " файлов");
// Создаем директории если нужно
try {
Files.createDirectories(instance.getPath());
} catch (IOException e) {
System.err.println(ZAnsi.red("Ошибка создания директорий: " + e.getMessage()));
return false;
}
// Удаляем файлы
for (String filePath : diff.getToDelete()) {
Path fullPath = instance.getPath().resolve(filePath);
try {
if (Files.deleteIfExists(fullPath)) {
System.out.println(ZAnsi.yellow(" Удален: " + filePath));
}
} catch (IOException e) {
System.err.println(ZAnsi.red(" Ошибка удаления " + filePath + ": " + e.getMessage()));
}
}
// Скачиваем файлы
AtomicInteger downloaded = new AtomicInteger(0);
int total = diff.getToDownload().size();
for (FileInfo file : diff.getToDownload()) {
String path = file.getPath();
Path fullPath = instance.getPath().resolve(path);
try {
// Создаем директории
Files.createDirectories(fullPath.getParent());
// Скачиваем файл
downloadFile(file, fullPath);
// Проверяем хеш
String actualHash = calculateHash(fullPath);
if (!actualHash.equals(file.getHash())) {
throw new IOException("Хеш не совпадает! Ожидался: " + file.getHash() +
", получен: " + actualHash);
}
downloaded.incrementAndGet();
if (total > 0) {
ProgressBar.show("Скачивание", downloaded.get(), total, "файлов");
}
} catch (Exception e) {
System.err.println("\n" + ZAnsi.red(" Ошибка скачивания " + path + ": " + e.getMessage()));
return false;
}
}
if (total > 0) {
ProgressBar.finish("Скачивание");
}
return true;
}
/**
* Скачать один файл с сервера
*/
private void downloadFile(FileInfo file, Path destination) throws Exception {
String url = ZHttpClient.getBaseUrl() + file.getUrl();
HttpRequest request = HttpRequest.newBuilder()
.uri(java.net.URI.create(url))
.GET()
.build();
HttpResponse<InputStream> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofInputStream());
if (response.statusCode() != 200) {
throw new IOException("HTTP " + response.statusCode());
}
// Скачиваем с прогрессом
try (InputStream in = response.body();
FileOutputStream out = new FileOutputStream(destination.toFile())) {
byte[] buffer = new byte[8192];
int bytesRead;
long totalRead = 0;
long fileSize = file.getSize();
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
totalRead += bytesRead;
if (fileSize > 0 && totalRead % 8192 == 0) {
ProgressBar.showDownload(" " + file.getPath(), totalRead, fileSize);
}
}
}
ProgressBar.clearLine();
}
/**
* Вычисление SHA256 хеша файла
*/
private String calculateHash(Path file) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
try (InputStream in = Files.newInputStream(file)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead);
}
}
byte[] hashBytes = digest.digest();
StringBuilder sb = new StringBuilder();
for (byte b : hashBytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
/**
* Парсинг даты из строки
*/
private LocalDateTime parseDateTime(String dateTimeStr) {
try {
return LocalDateTime.parse(dateTimeStr, DATE_FORMATTER);
} catch (Exception e) {
return null;
}
}
// ====================== Вложенные классы ======================
public static class PackManifest {
private String pack_name;
private int version;
private String minecraft_version;
private String loader_type;
private String loader_version;
private String asset_index;
private Map<String, Object> files;
public String getPackName() { return pack_name; }
public int getVersion() { return version; }
public String getMinecraftVersion() { return minecraft_version; }
public String getLoaderType() { return loader_type; }
public String getLoaderVersion() { return loader_version; }
public String getAssetIndex() { return asset_index != null ? asset_index : minecraft_version; }
public Map<String, Object> getFiles() { return files; }
public boolean isEmpty() { return files == null || files.isEmpty(); }
}
public static class DiffResponse {
private int version;
private List<FileInfo> to_download;
private List<String> to_delete;
private List<String> to_update;
public int getVersion() { return version; }
public List<FileInfo> getToDownload() { return to_download != null ? to_download : new ArrayList<>(); }
public List<String> getToDelete() { return to_delete != null ? to_delete : new ArrayList<>(); }
public List<String> getToUpdate() { return to_update != null ? to_update : new ArrayList<>(); }
}
public static class FileInfo {
private String path;
private String url;
private long size;
private String hash;
public String getPath() { return path; }
public String getUrl() { return url; }
public long getSize() { return size; }
public String getHash() { return hash; }
}
}
@@ -0,0 +1,46 @@
package me.sashegdev.zernmc.launcher.minecraft;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class ServerPack {
private final String name;
private final int version;
private final String minecraftVersion;
private final String loaderType;
private final String loaderVersion;
private final LocalDateTime updatedAt;
private final int filesCount;
public ServerPack(String name, int version, String minecraftVersion,
String loaderType, String loaderVersion,
LocalDateTime updatedAt, int filesCount) {
this.name = name;
this.version = version;
this.minecraftVersion = minecraftVersion;
this.loaderType = loaderType;
this.loaderVersion = loaderVersion;
this.updatedAt = updatedAt;
this.filesCount = filesCount;
}
public String getName() { return name; }
public int getVersion() { return version; }
public String getMinecraftVersion() { return minecraftVersion; }
public String getLoaderType() { return loaderType; }
public String getLoaderVersion() { return loaderVersion; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public int getFilesCount() { return filesCount; }
@Override
public String toString() {
if (updatedAt != null) {
return String.format("%s [%s + %s v%d] - %d файлов (обновлен: %s)",
name, minecraftVersion, loaderType, version, filesCount,
updatedAt.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")));
} else {
return String.format("%s [%s + %s v%d] - %d файлов",
name, minecraftVersion, loaderType, version, filesCount);
}
}
}
@@ -32,37 +32,56 @@ public class FabricInstaller {
// Шаг 1: Устанавливаем vanilla и получаем assetIndex
VersionInstaller versionInstaller = new VersionInstaller(instancePath);
String assetIndex = versionInstaller.install(minecraftVersion); // ← теперь String
if (assetIndex == null || assetIndex.isEmpty()) {
System.out.println(ZAnsi.brightRed("Не удалось установить Minecraft " + minecraftVersion));
return false;
String assetIndex = versionInstaller.install(minecraftVersion);
// ДОПОЛНИТЕЛЬНАЯ ПРОВЕРКА: если versionInstaller.install() вернул неправильный индекс
// (например, "1.20.1" вместо "5"), то получаем правильный индекс напрямую
if (assetIndex == null || assetIndex.isEmpty() || assetIndex.equals(minecraftVersion)) {
System.out.println(ZAnsi.yellow("Asset index из установки выглядит подозрительно: " + assetIndex));
System.out.println(ZAnsi.cyan("Получаем правильный asset index для " + minecraftVersion + "..."));
// Получаем правильный asset index из манифеста версии
String correctAssetIndex = versionInstaller.getAssetIndexForVersion(minecraftVersion);
if (correctAssetIndex != null && !correctAssetIndex.isEmpty()) {
assetIndex = correctAssetIndex;
System.out.println(ZAnsi.green("Правильный asset index: " + assetIndex));
} else {
System.out.println(ZAnsi.brightRed("Не удалось получить asset index для версии " + minecraftVersion));
return false;
}
}
// Сохраняем assetIndex
// Сохраняем правильный assetIndex
instance.setAssetIndex(assetIndex);
System.out.println(ZAnsi.green("Asset index сохранён: " + assetIndex));
// Шаг 2: Скачивание и запуск Fabric Installer
// Шаг 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 скачан");
if (!Files.exists(installerJar)) {
ProgressBar.show("Скачивание Fabric Installer", 0, 100, "%");
downloadFile(installerUrl, installerJar);
ProgressBar.finish("Fabric Installer скачан");
} else {
System.out.println(ZAnsi.green("Fabric Installer уже скачан, пропускаем..."));
}
System.out.println(ZAnsi.cyan("Запуск Fabric Installer..."));
// Используем ProcessBuilder с правильными аргументами
ProcessBuilder pb = new ProcessBuilder(
"java", "-jar", installerJar.toAbsolutePath().toString(),
"client",
"-dir", instancePath.toAbsolutePath().toString(),
"-mcversion", minecraftVersion,
"-loader", loaderVersion,
"-noprofile",
"-snapshot"
"-noprofile"
);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
@@ -74,25 +93,81 @@ public class FabricInstaller {
return false;
}
// Проверка результата
// Проверка результата - Fabric создаёт папку versions/fabric-loader-{loaderVersion}-{minecraftVersion}
String fabricVersionId = "fabric-loader-" + loaderVersion + "-" + minecraftVersion;
Path fabricVersionDir = instancePath.resolve("versions").resolve(fabricVersionId);
// Альтернативная проверка (иногда Fabric использует другой формат)
if (!Files.exists(fabricVersionDir)) {
fabricVersionId = "fabric-loader-" + loaderVersion + "-" + minecraftVersion;
fabricVersionDir = instancePath.resolve("versions").resolve(fabricVersionId);
}
if (Files.exists(fabricVersionDir)) {
System.out.println(ZAnsi.brightGreen("Fabric успешно установлен!"));
System.out.println("Версия: " + fabricVersionId);
System.out.println(ZAnsi.white("Версия: ") + fabricVersionId);
System.out.println(ZAnsi.white("Asset index: ") + assetIndex);
// Сохраняем метаданные в Instance
instance.setMinecraftVersion(minecraftVersion);
instance.setLoaderType("fabric");
instance.setLoaderVersion(loaderVersion);
// assetIndex уже сохранён выше, но сохраняем ещё раз для надёжности
instance.setAssetIndex(assetIndex);
// Копируем или создаём ссылку на правильный asset index в версии Fabric
ensureAssetIndexInFabricVersion(fabricVersionDir, assetIndex, minecraftVersion);
return true;
} else {
System.out.println(ZAnsi.brightRed("Fabric Installer отработал, но версия не найдена."));
System.out.println(ZAnsi.yellow("Искали: " + fabricVersionDir));
// Выводим содержимое папки versions для отладки
Path versionsDir = instancePath.resolve("versions");
if (Files.exists(versionsDir)) {
System.out.println(ZAnsi.cyan("Доступные версии в папке versions:"));
try (var stream = Files.list(versionsDir)) {
stream.forEach(p -> System.out.println(" - " + p.getFileName()));
}
}
return false;
}
}
/**
* Убеждаемся, что в JSON файле версии Fabric есть правильный asset index
*/
private void ensureAssetIndexInFabricVersion(Path fabricVersionDir, String assetIndex, String minecraftVersion) throws IOException {
Path versionJson = fabricVersionDir.resolve(fabricVersionDir.getFileName() + ".json");
if (!Files.exists(versionJson)) {
System.out.println(ZAnsi.yellow("JSON файл версии не найден: " + versionJson));
return;
}
String content = Files.readString(versionJson);
// Проверяем, есть ли в JSON правильный asset index
if (!content.contains("\"assets\":\"" + assetIndex + "\"")) {
System.out.println(ZAnsi.yellow("Исправляем asset index в JSON файле версии..."));
// Заменяем assets на правильное значение
// Ищем "assets": "что-то" и заменяем
content = content.replaceAll("\"assets\":\\s*\"[^\"]*\"", "\"assets\": \"" + assetIndex + "\"");
// Также проверяем assetIndex в downloads
if (content.contains("\"assetIndex\"")) {
content = content.replaceAll("\"assetIndex\":\\s*\"[^\"]*\"", "\"assetIndex\": \"" + assetIndex + "\"");
}
Files.writeString(versionJson, content);
System.out.println(ZAnsi.green("Asset index исправлен на: " + assetIndex));
} else {
System.out.println(ZAnsi.green("Asset index в JSON версии уже правильный: " + assetIndex));
}
}
private void cleanOldFabricLoaders() throws IOException {
Path librariesDir = instance.getPath().resolve("libraries/net/fabricmc/fabric-loader");
if (!Files.exists(librariesDir)) return;
@@ -101,12 +176,11 @@ public class FabricInstaller {
try (var stream = Files.walk(librariesDir)) {
stream.filter(Files::isDirectory)
.filter(dir -> dir.getFileName().toString().startsWith("0."))
.filter(dir -> dir.getFileName().toString().matches("\\d+\\.\\d+\\.\\d+.*"))
.forEach(dir -> {
try {
// Удаляем папку версии
Files.walk(dir)
.sorted((a,b) -> b.compareTo(a)) // удаляем файлы перед папками
.sorted((a,b) -> b.compareTo(a))
.forEach(p -> {
try { Files.deleteIfExists(p); }
catch (IOException ignored) {}
@@ -126,14 +200,31 @@ public class FabricInstaller {
}
private String downloadString(String url) throws Exception {
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).GET().build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(30))
.GET()
.build();
HttpResponse<String> resp = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() != 200) throw new IOException("HTTP " + resp.statusCode());
if (resp.statusCode() != 200) {
throw new IOException("HTTP " + resp.statusCode() + " при скачивании " + url);
}
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));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(60))
.GET()
.build();
HttpResponse<Path> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofFile(target));
if (response.statusCode() != 200) {
throw new IOException("HTTP " + response.statusCode() + " при скачивании " + url);
}
}
}
@@ -56,7 +56,7 @@ public class VersionInstaller {
return versions;
}
public String install(String versionId) throws Exception { // ← поменял boolean на String
public String install(String versionId) throws Exception {
System.out.println(ZAnsi.cyan("Полная установка Minecraft " + versionId + "..."));
Path versionDir = minecraftDir.resolve("versions").resolve(versionId);
Files.createDirectories(versionDir);
@@ -77,17 +77,20 @@ public class VersionInstaller {
System.out.println(ZAnsi.cyan("Скачивание библиотек..."));
downloadLibraries(versionData.getJSONArray("libraries"));
// Ассеты
String assetIndex = versionData.getString("assets"); // ← ВОТ ЭТО ГЛАВНОЕ!
// Ассеты - ЭТО ВАЖНО
String assetIndex = versionData.getString("assets"); // ← Например "5" для 1.20.1
System.out.println(ZAnsi.cyan("Asset index из версии: " + assetIndex));
if (versionData.has("assetIndex")) {
System.out.println(ZAnsi.cyan("Скачивание ассетов..."));
downloadAssets(versionData);
System.out.println(ZAnsi.brightGreen("Asset index определён как: " + assetIndex));
} else {
System.out.println(ZAnsi.yellow("Нет assetIndex в версии, использую fallback: " + assetIndex));
}
System.out.println(ZAnsi.brightGreen("\nMinecraft " + versionId + " полностью установлен!"));
return assetIndex; // ← возвращаем настоящий assetIndex (например "30")
return assetIndex; // ← Возвращаем правильный индекс (например "5")
}
private void downloadLibraries(JSONArray libraries) throws Exception {
@@ -188,6 +191,16 @@ public class VersionInstaller {
}
}
public String getAssetIndexForVersion(String versionId) throws Exception {
String versionUrl = getVersionUrl(versionId);
if (versionUrl == null) throw new Exception("Версия " + versionId + " не найдена");
String versionJson = downloadString(versionUrl);
JSONObject versionData = new JSONObject(versionJson);
return versionData.getString("assets");
}
private String getVersionUrl(String versionId) throws Exception {
for (MinecraftVersion v : getAvailableVersions()) {
if (v.getId().equals(versionId)) return v.getUrl();