Фиксы проходок (нормально, в отличии от main ветки)

ОНО РАБОТАЕТ СУКАААА
This commit is contained in:
Sashegdev
2026-04-20 19:30:17 +00:00
parent a3f9871d6e
commit 6bf6c1634a
11 changed files with 1025 additions and 414 deletions
+18
View File
@@ -91,6 +91,24 @@
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>global</id>
<properties>
<launcher.title>ZernMC Launcher</launcher.title>
<build.profile>global</build.profile>
<server.url>http://87.120.187.36:1582</server.url>
</properties>
</profile>
<profile>
<id>zernmc</id>
<properties>
<launcher.title>ZernMC Private Launcher</launcher.title>
<build.profile>zernmc</build.profile>
<server.url>http://87.120.187.36:1582</server.url>
</properties>
</profile>
</profiles>
<properties>
<maven.compiler.target>21</maven.compiler.target>
<mainClass>me.sashegdev.zernmc.launcher.Main</mainClass>
+25
View File
@@ -153,4 +153,29 @@
</plugin>
</plugins>
</build>
<profiles>
<!-- ==================== GLOBAL ==================== -->
<profile>
<id>global</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<build.profile>global</build.profile>
<launcher.title>ZernMC Launcher</launcher.title>
<server.url>http://87.120.187.36:1582</server.url>
<!-- Можно добавить флаги для отключения некоторых фич -->
</properties>
</profile>
<!-- ==================== ZERNMC ==================== -->
<profile>
<id>zernmc</id>
<properties>
<build.profile>zernmc</build.profile>
<launcher.title>ZernMC Private Launcher</launcher.title>
<server.url>http://87.120.187.36:1582</server.url>
</properties>
</profile>
</profiles>
</project>
@@ -4,7 +4,6 @@ import me.sashegdev.zernmc.launcher.auth.AuthManager;
import me.sashegdev.zernmc.launcher.menu.*;
import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
import me.sashegdev.zernmc.launcher.utils.*;
import java.io.IOException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
@@ -23,13 +22,12 @@ public class Main {
System.setProperty("file.encoding", "UTF-8");
System.setProperty("sun.err.encoding", "UTF-8");
System.setProperty("sun.stdout.encoding", "UTF-8");
java.nio.charset.Charset.defaultCharset();
ZAnsi.install();
ZAnsi.install();
System.out.print("\033[H\033[2J");
System.out.println(ZAnsi.brightGreen("Добро пожаловать в ZernMC Launcher " + CURRENT_VERSION));
//проверка всех сервисов при старте
// Проверка всех сервисов при старте
ZHttpClient.checkAllServicesOnStartup();
checkAndAutoUpdateLauncher();
@@ -49,8 +47,8 @@ public class Main {
} else {
System.out.println(ZAnsi.brightGreen("Добро пожаловать обратно, " + AuthManager.getUsername() + "!"));
}
// === КОНЕЦ АВТОРИЗАЦИИ ===
// === ГЛАВНЫЙ ЦИКЛ ===
try {
mainLoop();
} catch (Exception e) {
@@ -63,7 +61,6 @@ public class Main {
private static void checkAndAutoUpdateLauncher() {
System.out.println(ZAnsi.cyan("Проверка обновлений лаунчера..."));
try {
String json = ZHttpClient.getLauncherVersionInfo();
String serverVersion = extractVersion(json);
@@ -74,13 +71,11 @@ public class Main {
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());
@@ -109,9 +104,7 @@ public class Main {
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("Обновление успешно установлено!"));
}
@@ -152,27 +145,73 @@ public class Main {
}
}
// ====================== ГЛАВНЫЙ ЦИКЛ ======================
private static void mainLoop() throws Exception {
if (Config.isZernMCBuild()) {
zernMCFlow();
} else {
globalFlow();
}
}
// ====================== ZERNMC FLOW ======================
private static void zernMCFlow() throws Exception {
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.header("=== ZernMC Private Launcher ==="));
// 1. Проверка подключения к серверу
System.out.println(ZAnsi.cyan("Проверка подключения к ZernMC серверу..."));
try {
String response = ZHttpClient.get("/health");
System.out.println(ZAnsi.brightGreen("✓ Сервер доступен"));
} catch (Exception e) {
System.out.println(ZAnsi.brightRed("✗ Не удалось подключиться к ZernMC серверу"));
System.out.println(ZAnsi.white("Ошибка: " + e.getMessage()));
ConsoleUtils.pause();
System.exit(1);
}
// 2. Авторизация
boolean sessionRestored = AuthManager.loadSavedSession();
if (!sessionRestored) {
LoginMenu loginMenu = new LoginMenu();
boolean loggedIn = loginMenu.show();
if (!loggedIn) {
System.exit(0);
}
} else {
System.out.println(ZAnsi.brightGreen("Добро пожаловать обратно, " + AuthManager.getUsername() + "!"));
}
// 3. Запуск меню (LaunchMenu сам определит режим и вызовет нужный flow)
LaunchMenu launchMenu = new LaunchMenu();
launchMenu.show(); // ← Здесь будет вызван showZernMCOnly() внутри
}
// ====================== GLOBAL FLOW ======================
private static void globalFlow() throws Exception {
while (true) {
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.header("=== ZernMC Launcher ==="));
List<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 0 -> new LaunchMenu().show(); // обычный LaunchMenu
case 1 -> new UpdateMenu().show();
case 2 -> new SettingsMenu().show();
case 3 -> new ServerCheckMenu().show();
@@ -203,31 +203,23 @@ public class AuthManager {
if (!isLoggedIn()) return false;
try {
String response = ZHttpClient.get("/auth/pass/my");
return response.contains("\"is_active\":true");
JsonObject json = JsonParser.parseString(response).getAsJsonObject();
return json.has("has_active") && json.get("has_active").getAsBoolean();
} catch (Exception e) {
System.err.println("Не удалось проверить проходки: " + e.getMessage());
System.err.println(ZAnsi.red("Не удалось проверить проходки: ") + e.getMessage());
return false;
}
}
public static String activatePass(String passCode) {
public static String getPassStatus() {
if (!isLoggedIn()) return "Не авторизован";
try {
String json = "{\"pass_code\":\"" + passCode.toUpperCase() + "\"}";
SimpleHttpResponse resp = post("/auth/pass/activate", json);
System.out.println(ZAnsi.cyan("[AUTH] Активация проходки: HTTP " + resp.statusCode()));
if (resp.statusCode() == 200) {
return "Проходка успешно активирована!";
} else if (resp.statusCode() == 401) {
return "Ошибка: Требуется авторизация. Перезайдите в аккаунт.";
} else {
String error = extractError(resp.body());
return "Ошибка: " + error;
}
String response = ZHttpClient.get("/auth/pass/my");
JsonObject json = JsonParser.parseString(response).getAsJsonObject();
boolean hasActive = json.has("has_active") && json.get("has_active").getAsBoolean();
return hasActive ? "Есть активная проходка" : "Проходка отсутствует";
} catch (Exception e) {
e.printStackTrace();
return "Ошибка соединения: " + e.getMessage();
return "Ошибка проверки";
}
}
@@ -10,12 +10,15 @@ import me.sashegdev.zernmc.launcher.minecraft.installer.VersionInstaller;
import me.sashegdev.zernmc.launcher.minecraft.model.LaunchOptions;
import me.sashegdev.zernmc.launcher.minecraft.model.MinecraftVersion;
import me.sashegdev.zernmc.launcher.ui.ArrowMenu;
import me.sashegdev.zernmc.launcher.utils.Config;
import me.sashegdev.zernmc.launcher.utils.ConsoleUtils;
import me.sashegdev.zernmc.launcher.utils.Input;
import me.sashegdev.zernmc.launcher.utils.ZAnsi;
import me.sashegdev.zernmc.launcher.utils.ZHttpClient;
import java.awt.*;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -23,6 +26,151 @@ import java.util.stream.Collectors;
public class LaunchMenu {
public void show() throws Exception {
if (Config.isZernMCBuild()) {
showZernMCOnly();
} else {
showGlobal();
}
}
// ====================== ZERNMC BUILD ======================
private void showZernMCOnly() throws Exception {
while (true) {
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.header("=== ZernMC Private Launcher ==="));
System.out.println(ZAnsi.cyan("Доступны только серверные сборки"));
if (!awaitActivePass()) {
return;
}
PackDownloader tempDownloader = new PackDownloader(null);
List<ServerPack> availablePacks = tempDownloader.getAvailablePacks();
if (availablePacks.isEmpty()) {
System.out.println(ZAnsi.yellow("На данный момент нет доступных сборок на сервере."));
ConsoleUtils.pause();
return;
}
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);
installAndRunServerPack(selected);
}
}
private boolean awaitActivePass() throws Exception {
if (AuthManager.hasActivePass()) {
System.out.println(ZAnsi.brightGreen("✓ Активная проходка подтверждена"));
return true;
}
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.brightRed("У вас нет активной проходки!"));
System.out.println(ZAnsi.white("Для доступа к сборкам ZernMC требуется активная проходка."));
System.out.println();
openActivationWebsite();
System.out.println(ZAnsi.cyan("Ожидаем активацию проходки... (проверка каждые 10 секунд)"));
System.out.println(ZAnsi.white("Нажмите Enter для отмены"));
for (int i = 0; i < 60; i++) {
try {
if (System.in.available() > 0) {
Input.readLine();
System.out.println(ZAnsi.yellow("\nОжидание отменено."));
return false;
}
} catch (Exception ignored) {}
Thread.sleep(10000);
if (AuthManager.hasActivePass()) {
System.out.println(ZAnsi.brightGreen("\n✓ Проходка успешно активирована!"));
return true;
}
System.out.print(ZAnsi.cyan("."));
if ((i + 1) % 6 == 0) System.out.println();
}
System.out.println(ZAnsi.brightRed("\n\nВремя ожидания истекло."));
return false;
}
private void openActivationWebsite() {
//String url = "https://launcher.ru.zernmc.ru/activate-pass";
String url = ZHttpClient.getBaseUrl() + "/activate-pass";
try {
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Desktop.getDesktop().browse(new URI(url));
System.out.println(ZAnsi.cyan("Браузер открыт: " + url));
} else {
System.out.println(ZAnsi.yellow("Не удалось открыть браузер автоматически."));
System.out.println(ZAnsi.white("Откройте вручную: " + url));
}
} catch (Exception e) {
System.out.println(ZAnsi.brightRed("Ошибка открытия браузера: " + e.getMessage()));
System.out.println(ZAnsi.white("Ссылка: " + url));
}
}
private void installAndRunServerPack(ServerPack selected) throws Exception {
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.header("Установка сборки: " + selected.getName()));
System.out.println(ZAnsi.white(" Minecraft: ") + selected.getMinecraftVersion());
System.out.println(ZAnsi.white(" Лоадер: ") + selected.getLoaderType() +
(selected.getLoaderVersion() != null ? " " + selected.getLoaderVersion() : ""));
System.out.println(ZAnsi.white(" Версия: v") + selected.getVersion());
System.out.println(ZAnsi.white(" Файлов: ") + selected.getFilesCount());
String localName = askPackName();
if (localName == null) return;
if (InstanceManager.getInstance(localName) != null) {
System.out.println(ZAnsi.brightRed("Сборка с таким именем уже существует!"));
ConsoleUtils.pause();
return;
}
InstanceManager.createInstanceFolder(localName);
Instance newInstance = InstanceManager.getInstance(localName);
PackDownloader packDownloader = new PackDownloader(newInstance);
boolean success = packDownloader.installOrUpdatePack(selected.getName(), selected);
if (!success) {
System.out.println(ZAnsi.brightRed("\n[FAIL] Не удалось установить сборку."));
ConsoleUtils.pause();
return;
}
System.out.println(ZAnsi.brightGreen("\n[OK] Сборка '" + localName + "' успешно установлена!"));
ConsoleUtils.pause();
launchExistingInstance(newInstance);
}
// ====================== GLOBAL BUILD ======================
private void showGlobal() throws Exception {
while (true) {
ConsoleUtils.clearScreen();
List<Instance> instances = InstanceManager.getAllInstances();
@@ -37,11 +185,10 @@ public class LaunchMenu {
ArrowMenu menu = new ArrowMenu("Управление сборками", options);
int choice = menu.show();
if (choice == -1) break;
if (choice == options.size() - 1) break;
if (choice == -1 || choice == options.size() - 1) break;
if (choice == instances.size()) {
installNewPack();
installNewPackGlobal();
continue;
}
@@ -50,289 +197,101 @@ public class LaunchMenu {
}
}
private void installNewPack() throws Exception {
private void installNewPackGlobal() throws Exception {
ConsoleUtils.clearScreen();
List<String> options = List.of(
"Установить сборку с сервера ZernMC",
"Установить Vanilla Minecraft",
"Создать сборку вручную (Fabric/Forge)",
"Назад"
"Установить сборку с сервера 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 0 -> installServerPackGlobal();
case 1 -> createVanillaInstance();
case 2 -> createCustomInstance();
}
}
private void installServerPack() throws Exception {
if (!AuthManager.hasActivePass()) {
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.brightRed("У вас нет активной проходки!"));
System.out.println(ZAnsi.white("Чтобы скачивать сборки с сервера ZernMC, необходимо активировать проходку."));
System.out.println();
System.out.print(ZAnsi.white("Введите код проходки (ZERN-XXXXXXX) или Enter для отмены: "));
String code = Input.readLine();
if (code.isEmpty()) return;
String result = AuthManager.activatePass(code);
System.out.println(ZAnsi.cyan(result));
if (!result.contains("успешно")) {
ConsoleUtils.pause();
return;
}
// Повторная проверка
if (!AuthManager.hasActivePass()) {
System.out.println(ZAnsi.brightRed("Не удалось активировать проходку."));
ConsoleUtils.pause();
return;
}
}
private void installServerPackGlobal() throws Exception {
if (!awaitActivePass()) return;
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.cyan("Получение списка доступных сборок с сервера..."));
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());
.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();
}
// Проверяем, существует ли уже такая сборка
System.out.print(ZAnsi.white("\nВведите название локальной сборки (Enter = имя пака): "));
String localName = Input.readLine().trim();
if (localName.isEmpty()) localName = selected.getName();
if (InstanceManager.getInstance(localName) != null) {
System.out.println(ZAnsi.brightRed("Сборка с таким именем уже существует!"));
ConsoleUtils.pause();
return;
}
// Создаем инстанс
InstanceManager.createInstanceFolder(localName);
Instance newInstance = InstanceManager.getInstance(localName);
// Устанавливаем сборку
PackDownloader packDownloader = new PackDownloader(newInstance);
boolean success = packDownloader.installOrUpdatePack(selected.getName(), selected);
if (success) {
System.out.println(ZAnsi.brightGreen("\n[OK] Сборка '" + localName + "' успешно установлена!"));
} else {
System.out.println(ZAnsi.brightRed("\n[FAIL] Не удалось установить сборку."));
}
ConsoleUtils.pause();
}
private void createVanillaInstance() 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();
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();
}
// ====================== Вспомогательные методы ======================
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) {
return version.matches("^1\\.(1[4-9]|[2-9]\\d).*");
}
private boolean isForgeSupported(String version) {
if (version.matches("^1\\.2[2-9].*") || version.matches("^\\d{2}.*")) {
return false;
}
return version.matches("^1\\.(1[2-9]|[2-9]\\d).*") ||
version.matches("^1\\.20.*") || version.matches("^1\\.21.*");
}
// ====================== manageInstance — полностью восстановлен ======================
private void manageInstance(Instance instance) throws Exception {
while (true) {
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.header("Управление сборкой: " + instance.getName()));
System.out.println(ZAnsi.white("Версия: " + instance.getMinecraftVersion()));
System.out.println(ZAnsi.white("Лоадер: " + instance.getLoaderType() +
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()) {
@@ -341,12 +300,12 @@ public class LaunchMenu {
options.add("Изменить версию лоадера");
options.add("Удалить сборку");
options.add("Назад");
ArrowMenu menu = new ArrowMenu("Действия", options);
int choice = menu.show();
if (choice == -1 || choice == options.size() - 1) return;
switch (choice) {
case 0 -> launchExistingInstance(instance);
case 1 -> {
@@ -367,20 +326,20 @@ public class LaunchMenu {
}
}
}
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());
@@ -392,44 +351,43 @@ public class LaunchMenu {
} 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 + "..."));
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 {
@@ -438,25 +396,25 @@ public class LaunchMenu {
} catch (Exception e) {
System.out.println(ZAnsi.brightRed("Ошибка при смене лоадера: " + e.getMessage()));
}
ConsoleUtils.pause();
}
private void deleteInstance(Instance instance) throws IOException {
ConsoleUtils.clearScreen();
List<String> confirmOptions = List.of(
"Да, удалить сборку",
"Нет, отменить"
"Да, удалить сборку",
"Нет, отменить"
);
ArrowMenu confirmMenu = new ArrowMenu(
"Вы действительно хотите удалить сборку '" + instance.getName() + "'?",
confirmOptions
"Вы действительно хотите удалить сборку '" + instance.getName() + "'?",
confirmOptions
);
int choice = confirmMenu.show();
if (choice == 0) {
boolean deleted = InstanceManager.deleteInstance(instance.getName());
if (deleted) {
@@ -467,67 +425,10 @@ 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)
.map(v -> "Fabric Loader " + v)
.collect(Collectors.toList());
options.add("Назад");
ArrowMenu menu = new ArrowMenu("Выбор версии Fabric Loader", options);
int choice = menu.show();
if (choice == -1 || choice == options.size() - 1) return null;
return versions.get(choice);
}
private String askForgeVersion(String mcVersion) throws Exception {
System.out.println(ZAnsi.cyan("Получение списка версий Forge для " + mcVersion + "..."));
List<String> allForgeVersions = getAllForgeVersions();
List<String> compatibleVersions = allForgeVersions.stream()
.filter(v -> v.startsWith(mcVersion + "-"))
.map(v -> v.substring(mcVersion.length() + 1))
.collect(Collectors.toList());
if (compatibleVersions.isEmpty()) {
System.out.println(ZAnsi.yellow("Не найдено совместимых версий Forge для " + mcVersion));
ConsoleUtils.pause();
return null;
}
List<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();
if (name.isEmpty()) {
System.out.println(ZAnsi.yellow("Отменено."));
return null;
}
return name;
}
private void launchExistingInstance(Instance instance) {
if (instance.isServerPack() && !AuthManager.hasActivePass()) {
ConsoleUtils.clearScreen();
@@ -535,47 +436,236 @@ public class LaunchMenu {
ConsoleUtils.pause();
return;
}
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.brightGreen("Запуск сборки: " + instance.getName()));
MinecraftLib lib = new MinecraftLib(instance);
LaunchOptions options = new LaunchOptions();
// Авторизация Minecraft
LaunchOptions options = new LaunchOptions();
options.setUsername(AuthManager.getUsername());
options.setUuid(AuthManager.getUuid());
options.setAccessToken(AuthManager.getAccessToken());
try {
lib.launch(options);
} catch (Exception e) {
System.out.println(ZAnsi.brightRed("Ошибка при запуске: " + e.getMessage()));
e.printStackTrace();
}
ConsoleUtils.pause();
}
// ====================== Остальные вспомогательные методы ======================
private String askPackName() {
System.out.print(ZAnsi.white("\nВведите название новой сборки: "));
String name = Input.readLine().trim();
if (name.isEmpty()) {
System.out.println(ZAnsi.yellow("Отменено."));
return null;
}
return name;
}
private void createVanillaInstance() throws Exception {
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.cyan("Получение списка версий Minecraft..."));
VersionInstaller versionInstaller = new VersionInstaller(null);
List<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 = loaderType.equals("fabric")
? askFabricLoaderVersion()
: 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();
}
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) {
return version.matches("^1\\.(1[4-9]|[2-9]\\d).*");
}
private boolean isForgeSupported(String version) {
if (version.matches("^1\\.2[2-9].*") || version.matches("^\\d{2}.*")) return false;
return version.matches("^1\\.(1[2-9]|[2-9]\\d).*") ||
version.matches("^1\\.20.*") || version.matches("^1\\.21.*");
}
private String askFabricLoaderVersion() throws Exception {
System.out.println(ZAnsi.cyan("Получение списка версий Fabric Loader..."));
List<String> versions = ZHttpClient.getFabricLoaderVersions();
List<String> options = versions.stream()
.limit(30)
.map(v -> "Fabric Loader " + v)
.collect(Collectors.toList());
options.add("Назад");
ArrowMenu menu = new ArrowMenu("Выбор версии Fabric Loader", options);
int choice = menu.show();
if (choice == -1 || choice == options.size() - 1) return null;
return versions.get(choice);
}
private String askForgeVersion(String mcVersion) throws Exception {
System.out.println(ZAnsi.cyan("Получение списка версий Forge для " + mcVersion + "..."));
List<String> allForgeVersions = getAllForgeVersions();
List<String> compatibleVersions = allForgeVersions.stream()
.filter(v -> v.startsWith(mcVersion + "-"))
.map(v -> v.substring(mcVersion.length() + 1))
.collect(Collectors.toList());
if (compatibleVersions.isEmpty()) {
System.out.println(ZAnsi.yellow("Не найдено совместимых версий Forge для " + mcVersion));
ConsoleUtils.pause();
return null;
}
List<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 List<String> getAllForgeVersions() throws Exception {
String metadataUrl = "https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml";
String xml = ZHttpClient.downloadString(metadataUrl);
String xml = ZHttpClient.downloadString("https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.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;
}
}
@@ -148,14 +148,46 @@ public class LoginMenu {
* Читаем пароль — стараемся скрыть вывод через Console,
* если недоступно (IDE/терминал без TTY) — читаем обычным способом.
*/
private String readPassword(String prompt) {
java.io.Console console = System.console();
if (console != null) {
char[] chars = console.readPassword(prompt);
return chars != null ? new String(chars) : "";
private String readPassword(String prompt) throws IOException {
// Создаём временный терминал для ввода пароля
org.jline.terminal.Terminal passTerminal = org.jline.terminal.TerminalBuilder.builder()
.system(true)
.jna(true)
.build();
passTerminal.enterRawMode();
passTerminal.writer().print(prompt);
passTerminal.writer().flush();
StringBuilder password = new StringBuilder();
try {
while (true) {
int key = passTerminal.reader().read();
if (key == 13 || key == 10) { // Enter
passTerminal.writer().println();
break;
} else if (key == 127 || key == 8) { // Backspace
if (password.length() > 0) {
password.setLength(password.length() - 1);
passTerminal.writer().print("\b \b");
passTerminal.writer().flush();
}
} else if (key == 3) { // Ctrl+C
passTerminal.writer().println();
System.exit(0);
} else if (key >= 32 && key < 127) { // Печатные символы
password.append((char) key);
passTerminal.writer().print('*');
passTerminal.writer().flush();
}
}
} finally {
passTerminal.close();
}
// Fallback: в IDE пароль будет виден
return Input.readLine(prompt);
return password.toString();
}
private void printBanner() {
@@ -16,71 +16,83 @@ import java.util.List;
public class ServerCheckMenu {
public void show() throws IOException {
List<String> options = List.of(
"Проверить подключение к ZernMC серверу",
"Проверить доступ к Mojang (Minecraft)",
"Проверить доступ к Fabric Meta",
"Назад в главное меню"
);
while (true) {
ConsoleUtils.clearScreen();
System.out.println(ZAnsi.header("Диагностика подключения"));
ArrowMenu menu = new ArrowMenu("Диагностика подключения", options);
int choice = menu.show();
List<String> options = List.of(
"Проверить подключение к ZernMC серверу",
"Проверить доступ к Mojang (Minecraft)",
"Проверить доступ к Fabric Meta",
"Проверить доступ к Forge Maven",
"Назад в главное меню"
);
if (choice == -1 || choice == 4) return;
ArrowMenu menu = new ArrowMenu("Выберите проверку", options);
int choice = menu.show();
ConsoleUtils.clearScreen();
if (choice == -1 || choice == 4) {
return;
}
switch (choice) {
case 0 -> checkZernServer();
case 1 -> checkMojang();
case 2 -> checkFabric();
ConsoleUtils.clearScreen();
switch (choice) {
case 0 -> checkZernServer();
case 1 -> checkMojang();
case 2 -> checkFabric();
case 3 -> checkForge();
}
ConsoleUtils.pause();
}
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);
System.out.println(ZAnsi.brightGreen("[OK] ZernMC сервер успешно подключён!"));
System.out.println(ZAnsi.white("Ответ сервера: ") + response);
} catch (Exception e) {
System.out.println(ZAnsi.brightRed("Не удалось подключиться к ZernMC серверу"));
System.out.println("Ошибка: " + e.getMessage());
System.out.println(ZAnsi.brightRed("[FAIL] Не удалось подключиться к ZernMC серверу"));
System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
}
}
private void checkMojang() {
System.out.println(ZAnsi.cyan("Проверка доступа к Mojang..."));
try {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(8))
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"))
.uri(URI.create("https://piston-meta.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 доступен"));
System.out.println(ZAnsi.brightGreen("[OK] Mojang доступен"));
} else {
System.out.println(ZAnsi.brightRed("Mojang вернул код " + response.statusCode()));
System.out.println(ZAnsi.brightRed("[FAIL] Mojang вернул код " + response.statusCode()));
}
} catch (Exception e) {
System.out.println(ZAnsi.brightRed("Нет доступа к Mojang"));
System.out.println("Ошибка: " + e.getMessage());
System.out.println(ZAnsi.brightRed("[FAIL] Нет доступа к Mojang"));
System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
}
}
private void checkFabric() {
System.out.println(ZAnsi.cyan("Проверка доступа к Fabric Meta..."));
try {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(8))
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpRequest request = HttpRequest.newBuilder()
@@ -91,13 +103,39 @@ public class ServerCheckMenu {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
System.out.println(ZAnsi.brightGreen("Fabric Meta доступен"));
System.out.println(ZAnsi.brightGreen("[OK] Fabric Meta доступен"));
} else {
System.out.println(ZAnsi.brightRed("Fabric Meta вернул код " + response.statusCode()));
System.out.println(ZAnsi.brightRed("[FAIL] Fabric Meta вернул код " + response.statusCode()));
}
} catch (Exception e) {
System.out.println(ZAnsi.brightRed("Нет доступа к Fabric Meta"));
System.out.println("Ошибка: " + e.getMessage());
System.out.println(ZAnsi.brightRed("[FAIL] Нет доступа к Fabric Meta"));
System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
}
}
private void checkForge() {
System.out.println(ZAnsi.cyan("Проверка доступа к Forge Maven..."));
try {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml"))
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
System.out.println(ZAnsi.brightGreen("[OK] Forge Maven доступен"));
} else {
System.out.println(ZAnsi.brightRed("[FAIL] Forge Maven вернул код " + response.statusCode()));
}
} catch (Exception e) {
System.out.println(ZAnsi.brightRed("[FAIL] Нет доступа к Forge Maven"));
System.out.println(ZAnsi.white("Ошибка: ") + e.getMessage());
}
}
}
@@ -10,6 +10,8 @@ public class Config {
private static final Path CONFIG_DIR = Path.of(System.getProperty("user.home"), ".zernmc");
private static final Path CONFIG_FILE = CONFIG_DIR.resolve("launcher.properties");
private static final String BUILD_PROFILE = System.getProperty("build.profile", "global");
private static final Properties props = new Properties();
// Настройки
@@ -83,6 +85,14 @@ public class Config {
return maxMemory;
}
public static boolean isZernMCBuild() {
return "zernmc".equalsIgnoreCase(BUILD_PROFILE);
}
public static boolean isGlobalBuild() {
return !isZernMCBuild();
}
public static void setMaxMemory(int memory) {
// Защита от слишком маленьких/больших значений
if (memory < 1024) memory = 1536;
@@ -19,6 +19,7 @@ public class Input {
}
public static String readLine(String prompt) {
flushInput(); // Очищаем буфер
System.out.print(prompt);
return scanner.nextLine().trim();
}
@@ -79,4 +80,18 @@ public class Input {
public static void close() {
scanner.close();
}
/**
* Очищает буфер ввода от оставшихся символов
*/
public static void flushInput() {
try {
while (System.in.available() > 0) {
System.in.read();
}
} catch (IOException e) {
// Игнорируем
}
}
}
@@ -3,6 +3,8 @@ package me.sashegdev.zernmc.launcher.utils;
import org.json.JSONArray;
import org.json.JSONObject;
import me.sashegdev.zernmc.launcher.auth.AuthManager;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
@@ -380,13 +382,19 @@ public class ZHttpClient {
}
try {
HttpRequest request = HttpRequest.newBuilder()
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + endpoint))
.timeout(Duration.ofSeconds(15))
.header("User-Agent", "ZernMC-Launcher/1.0")
.GET()
.build();
.GET();
// ===== ДОБАВИТЬ ТОКЕН АВТОРИЗАЦИИ =====
String accessToken = AuthManager.getAccessToken();
if (accessToken != null && !accessToken.equals("0")) {
requestBuilder.header("Authorization", "Bearer " + accessToken);
}
HttpRequest request = requestBuilder.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
@@ -401,19 +409,25 @@ public class ZHttpClient {
private static String proxyGet(String endpoint) throws IOException {
try {
HttpRequest request = HttpRequest.newBuilder()
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/proxy" + endpoint))
.timeout(Duration.ofSeconds(30))
.header("User-Agent", "ZernMC-Launcher/1.0")
.GET()
.build();
.GET();
// ===== ДОБАВИТЬ ТОКЕН АВТОРИЗАЦИИ =====
String accessToken = AuthManager.getAccessToken();
if (accessToken != null && !accessToken.equals("0")) {
requestBuilder.header("Authorization", "Bearer " + accessToken);
}
HttpRequest request = requestBuilder.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IOException("HTTP " + response.statusCode());
}
proxySuccessCount++;
return response.body();
} catch (Exception e) {
+338
View File
@@ -15,6 +15,8 @@ from middleware import LoggingMiddleware
from cli import parse_args, run_test_mode, run_production_mode, run_development_mode
from log_manager import init_logging
from fastapi.responses import Response
import httpx
import base64
from fastapi.responses import StreamingResponse
@@ -80,6 +82,331 @@ async def lifespan(app: FastAPI):
logger.info("Server shutting down...")
# ====================== ШАБЛОН СТРАНИЦЫ АКТИВАЦИИ ======================
ACTIVATE_PASS_HTML = """
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Активация проходки | ZernMC</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 40px;
max-width: 450px;
width: 100%;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.logo {
text-align: center;
margin-bottom: 30px;
}
.logo h1 {
color: #00d4ff;
font-size: 32px;
font-weight: 700;
text-shadow: 0 0 20px rgba(0, 212, 255, 0.5);
}
.logo p {
color: #8892b0;
margin-top: 8px;
}
.form-group {
margin-bottom: 24px;
}
label {
display: block;
color: #ccd6f6;
margin-bottom: 8px;
font-weight: 500;
}
input {
width: 100%;
padding: 14px 16px;
background: rgba(255, 255, 255, 0.07);
border: 1.5px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
color: #ffffff;
font-size: 16px;
transition: all 0.3s ease;
outline: none;
}
input:focus {
border-color: #00d4ff;
background: rgba(255, 255, 255, 0.1);
box-shadow: 0 0 0 4px rgba(0, 212, 255, 0.1);
}
input::placeholder {
color: #8892b0;
opacity: 0.6;
}
button {
width: 100%;
padding: 14px;
background: linear-gradient(135deg, #00d4ff 0%, #0099cc 100%);
border: none;
border-radius: 12px;
color: white;
font-size: 18px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 10px;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px -5px rgba(0, 212, 255, 0.4);
}
button:active {
transform: translateY(0);
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
#message {
margin-top: 20px;
padding: 14px;
border-radius: 12px;
display: none;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.success {
background: rgba(0, 255, 100, 0.15);
border: 1px solid rgba(0, 255, 100, 0.3);
color: #00ff64;
}
.error {
background: rgba(255, 50, 50, 0.15);
border: 1px solid rgba(255, 50, 50, 0.3);
color: #ff5050;
}
.info {
background: rgba(0, 212, 255, 0.15);
border: 1px solid rgba(0, 212, 255, 0.3);
color: #00d4ff;
}
.spinner {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 0.6s linear infinite;
margin-left: 8px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.footer {
text-align: center;
margin-top: 24px;
color: #8892b0;
font-size: 14px;
}
.footer a {
color: #00d4ff;
text-decoration: none;
}
.footer a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<div class="logo">
<h1>ZernMC</h1>
<p>Активация проходки</p>
</div>
<form id="activateForm">
<div class="form-group">
<label for="username">Ваш никнейм</label>
<input
type="text"
id="username"
name="username"
placeholder="Введите ваш ник в игре"
required
minlength="3"
maxlength="16"
pattern="[a-zA-Z0-9_]+"
autocomplete="off"
>
</div>
<div class="form-group">
<label for="passCode">Код проходки</label>
<input
type="text"
id="passCode"
name="passCode"
placeholder="XXXX-XXXX-XXXX"
required
minlength="8"
maxlength="20"
autocomplete="off"
style="text-transform: uppercase;"
>
</div>
<button type="submit" id="submitBtn">
Активировать проходку
</button>
</form>
<div id="message"></div>
<div class="footer">
<p>Нет проходки? <a href="https://zernmc.ru" target="_blank">Получить на сайте</a></p>
</div>
</div>
<script>
const form = document.getElementById('activateForm');
const submitBtn = document.getElementById('submitBtn');
const messageDiv = document.getElementById('message');
const usernameInput = document.getElementById('username');
const passCodeInput = document.getElementById('passCode');
passCodeInput.addEventListener('input', (e) => {
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9-]/g, '');
});
function showMessage(text, type) {
messageDiv.textContent = text;
messageDiv.className = '';
messageDiv.classList.add(type);
messageDiv.style.display = 'block';
if (type === 'success' || type === 'info') {
setTimeout(() => {
messageDiv.style.display = 'none';
}, 5000);
}
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
const username = usernameInput.value.trim();
const passCode = passCodeInput.value.trim().toUpperCase();
const password = prompt("Введите пароль от вашего аккаунта " + username + ":");
if (!password) {
showMessage('Необходимо ввести пароль', 'error');
return;
}
if (!username || !passCode) {
showMessage('Пожалуйста, заполните все поля', 'error');
return;
}
submitBtn.disabled = true;
const originalText = submitBtn.textContent;
submitBtn.innerHTML = 'Активация... <span class="spinner"></span>';
messageDiv.style.display = 'none';
try {
// Логинимся с существующим аккаунтом
const loginResponse = await fetch('/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: username, password: password })
});
if (!loginResponse.ok) {
const errorData = await loginResponse.json();
throw new Error(errorData.detail || 'Неверный логин или пароль');
}
const tokenData = await loginResponse.json();
// Активируем проходку
const activateResponse = await fetch('/auth/pass/activate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${tokenData.access_token}`
},
body: JSON.stringify({ pass_code: passCode })
});
if (!activateResponse.ok) {
const errorData = await activateResponse.json();
throw new Error(errorData.detail || 'Ошибка активации проходки');
}
const activateData = await activateResponse.json();
showMessage('' + activateData.message, 'success');
form.reset();
} catch (error) {
console.error('Error:', error);
showMessage('' + error.message, 'error');
} finally {
submitBtn.disabled = false;
submitBtn.textContent = originalText;
}
});
</script>
</body>
</html>
"""
# Create app with lifespan
app = FastAPI(title="ZernMC Launcher Server", lifespan=lifespan)
@@ -144,6 +471,17 @@ async def health():
return {"status": "healthy", "timestamp": datetime.utcnow().isoformat()}
# ====================== WEB ИНТЕРФЕЙС ДЛЯ АКТИВАЦИИ ПРОХОДКИ ======================
@app.get("/activate-pass")
async def activate_pass_page():
"""Веб-интерфейс для активации проходки"""
return Response(
content=ACTIVATE_PASS_HTML,
media_type="text/html"
)
# ====================== ЭНДПОИНТЫ ДЛЯ ПАКОВ ======================
@app.get("/packs")