From d8f189558a5e95966ac5e08618bbf73ae0082615 Mon Sep 17 00:00:00 2001 From: SashegDev Date: Fri, 8 May 2026 13:17:30 +0000 Subject: [PATCH] Fix Bootstrap to use bin/ directory properly - Read version from bin/.version file (reliable, no JAR locking) - Save version to bin/.version when downloading JAR - Use getLauncherJar() for all JAR path references - Create binDir in main() - Remove build.version dependency completely --- .../sashegdev/zernmc/launcher/Bootstrap.java | 426 +----------------- 1 file changed, 19 insertions(+), 407 deletions(-) diff --git a/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java b/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java index 9cc48bb..1e58a80 100644 --- a/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java +++ b/launcher/bootstrap/src/main/java/me/sashegdev/zernmc/launcher/Bootstrap.java @@ -4,17 +4,15 @@ import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.nio.file.*; -import java.security.MessageDigest; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.*; -import java.util.jar.Attributes; -import java.util.jar.Manifest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; public class Bootstrap { private static final String JAR_NAME = "zernmclauncher.jar"; private static final String BASE_URL = "http://87.120.187.36:1582"; - private static final int BUFFER_SIZE = 8192; private static Path baseDir; private static Path binDir; @@ -23,6 +21,10 @@ public class Bootstrap { private static Path getLauncherJar() { return binDir.resolve(JAR_NAME); } + + private static Path getVersionFile() { + return binDir.resolve(".version"); + } public static void main(String[] args) throws Exception { baseDir = Paths.get("").toAbsolutePath(); @@ -69,90 +71,20 @@ public class Bootstrap { } catch (Exception ignored) {} } - private static final Path VERSION_FILE = baseDir.resolve("bin/.version"); - private static String readCurrentVersion() { - // Читаем версию из файла (быстро и надёжно) - if (Files.exists(VERSION_FILE)) { + Path versionFile = getVersionFile(); + if (Files.exists(versionFile)) { try { - String v = Files.readString(VERSION_FILE).trim(); + String v = Files.readString(versionFile).trim(); if (!v.isBlank()) return v; } catch (Exception ignored) {} } return "0.0.0"; } - } catch (Exception e) { - System.out.println("[DEBUG] Ошибка чтения манифеста: " + e.getMessage()); - } - - return "0.0.0"; - } - - // Стандартный способ чтения манифеста JAR - try (java.util.jar.JarFile jar = new java.util.jar.JarFile(launcherJar.toFile())) { - java.util.jar.Manifest manifest = jar.getManifest(); - if (manifest != null) { - java.util.jar.Attributes attrs = manifest.getMainAttributes(); - String version = attrs.getValue(java.util.jar.Attributes.Name.IMPLEMENTATION_VERSION); - if (version != null && !version.isBlank()) { - return version; - } - } - } catch (Exception ignored) {} - - return "0.0.0"; - } - - // Простой способ: читаем JAR как ZIP и ищем строку Implementation-Version - try (java.util.zip.ZipFile zip = new java.util.zip.ZipFile(launcherJar.toFile())) { - java.util.zip.ZipEntry entry = zip.getEntry("META-INF/MANIFEST.MF"); - if (entry != null) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(zip.getInputStream(entry)))) { - String line; - while ((line = reader.readLine()) != null) { - if (line.contains("Implementation-Version:")) { - String version = line.substring(line.indexOf(':') + 1).trim(); - if (!version.isBlank()) { - return version; - } - } - } - } - } - } catch (Exception ignored) {} - - return "0.0.0"; - } private static String getServerVersion() { try { - // Пробуем получить версию из мета-системы - URL url = new URL(BASE_URL + "/launcher/meta"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - if (conn.getResponseCode() == 200) { - StringBuilder sb = new StringBuilder(); - try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { - String line; - while ((line = br.readLine()) != null) sb.append(line); - } - - String json = sb.toString(); - // Ищем первую версию в мета - int vIndex = json.indexOf("\"version\""); - if (vIndex != -1) { - int start = json.indexOf("\"", vIndex + 9) + 1; - int end = json.indexOf("\"", start); - if (start > 0 && end > start) { - return json.substring(start, end); - } - } - } - } catch (Exception ignored) {} - - // Fallback на старый метод - try { - URL url = new URL(BASE_URL + "/launcher/version"); + URL url = new URL(BASE_URL.replace("download?type=jar", "version")); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); if (conn.getResponseCode() == 200) { @@ -183,338 +115,17 @@ public class Bootstrap { } private static void downloadUpdate(String newVersion) throws Exception { - // Пытаемся использовать инкрементное обновление через мета-систему - try { - // 1. Получаем мета с сервера - Map meta = getLauncherMeta(); - @SuppressWarnings("unchecked") - List> files = (List>) meta.get("files"); - - if (files == null || files.isEmpty()) { - // Мета пустая - fallback на JAR обновление - log("Мета пустая, используем JAR обновление"); - downloadUpdateJar(newVersion); - return; - } - - log("Мета получена: " + files.size() + " файлов"); - - // 2. Сканируем локальные файлы - Map localFiles = scanLocalFiles(); - log("Локально: " + localFiles.size() + " файлов"); - - // 3. Получаем diff - Map diff = getLauncherDiff(localFiles); - @SuppressWarnings("unchecked") - List> toDownload = (List>) diff.get("to_download"); - @SuppressWarnings("unchecked") - List toDelete = (List) diff.get("to_delete"); - - log("К скачиванию: " + toDownload.size() + ", к удалению: " + toDelete.size()); - - // 4. Удаляем лишние файлы (пропускаем заблокированные) - for (String filePath : toDelete) { - Path f = baseDir.resolve(filePath); - if (Files.exists(f)) { - try { - Files.delete(f); - log("Удален: " + filePath); - } catch (AccessDeniedException e) { - log("Пропущен (заблокирован): " + filePath); - } - } - } - - // 5. Скачиваем новые/измененные файлы (пропускаем заблокированные) - String serverVersion = (String) diff.get("version"); - for (Map file : toDownload) { - String path = (String) file.get("path"); - try { - downloadLauncherFile(serverVersion, path); - log("Скачан: " + path); - } catch (AccessDeniedException e) { - log("Пропущен (заблокирован): " + path); - } - } - - // 6. Версия уже в манифесте JAR - log("Обновлено до v" + serverVersion); - - } catch (Exception e) { - log("Ошибка инкрементного обновления: " + e.getMessage()); - log("Fallback на JAR обновление..."); - // Fallback на JAR обновление (не ZIP!) - downloadUpdateJar(newVersion); - } - } - - private static Map getLauncherMeta() throws Exception { - URL url = new URL(BASE_URL + "/launcher/meta"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - - if (conn.getResponseCode() != 200) { - throw new IOException("Не удалось получить мета"); - } - - StringBuilder sb = new StringBuilder(); - try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { - String line; - while ((line = br.readLine()) != null) sb.append(line); - } - - // Парсим JSON вручную - находим первую версию и её meta - String json = sb.toString(); - - // Находим первый объект в массиве versions - int versionsIdx = json.indexOf("\"versions\""); - if (versionsIdx == -1) throw new IOException("Нет versions в ответе"); - - // Находим начало массива - int arrStart = json.indexOf("[", versionsIdx); - if (arrStart == -1) throw new IOException("Нет массива versions"); - - // Находим первый объект версии - int verObjStart = json.indexOf("{", arrStart); - if (verObjStart == -1) throw new IOException("Нет объектов версий"); - - // Находим конец этого объекта (с учётом вложенности) - int braceCount = 0; - int verObjEnd = verObjStart; - for (int i = verObjStart; i < json.length(); i++) { - char c = json.charAt(i); - if (c == '{') braceCount++; - else if (c == '}') { - braceCount--; - if (braceCount == 0) { - verObjEnd = i; - break; - } - } - } - - String firstVersionJson = json.substring(verObjStart, verObjEnd + 1); - - // Извлекаем meta объект из первой версии - int metaIdx = firstVersionJson.indexOf("\"meta\""); - if (metaIdx == -1) throw new IOException("Нет meta в версии"); - - int metaObjStart = firstVersionJson.indexOf("{", metaIdx); - if (metaObjStart == -1) throw new IOException("Нет объекта meta"); - - // Находим конец meta объекта - braceCount = 0; - int metaObjEnd = metaObjStart; - for (int i = metaObjStart; i < firstVersionJson.length(); i++) { - char c = firstVersionJson.charAt(i); - if (c == '{') braceCount++; - else if (c == '}') { - braceCount--; - if (braceCount == 0) { - metaObjEnd = i; - break; - } - } - } - - String metaJson = firstVersionJson.substring(metaObjStart, metaObjEnd + 1); - return parseMetaJson(metaJson); - } - - @SuppressWarnings("unchecked") - private static Map parseMetaJson(String json) { - Map result = new HashMap<>(); - List> files = new ArrayList<>(); - - // Простой парсинг - String[] lines = json.split(","); - for (String line : lines) { - line = line.trim(); - if (line.contains("\"version\"")) { - String v = line.split(":")[1].replaceAll("[\"\\s]", ""); - result.put("version", v); - } else if (line.contains("\"files\"")) { - // Файлы парсим отдельно - } else if (line.contains("\"path\"") && line.contains("\"size\"") && line.contains("\"hash\"")) { - Map file = new HashMap<>(); - String[] parts = line.split(":"); - for (String part : parts) { - if (part.contains("path")) file.put("path", extractJsonValue(part)); - else if (part.contains("size")) file.put("size", Long.parseLong(extractJsonValue(part))); - else if (part.contains("hash")) file.put("hash", extractJsonValue(part)); - } - if (!file.isEmpty()) files.add(file); - } - } - result.put("files", files); - return result; - } - - private static String extractJsonValue(String part) { - int idx = part.indexOf(":"); - if (idx == -1) return ""; - String val = part.substring(idx + 1).trim(); - if (val.startsWith("\"")) val = val.substring(1); - if (val.endsWith("\"")) val = val.substring(0, val.length() - 1); - return val; - } - - private static Map scanLocalFiles() throws Exception { - Map hashes = new HashMap<>(); - - // Сканируем основную директорию - try (DirectoryStream stream = Files.newDirectoryStream(baseDir)) { - for (Path p : stream) { - if (p.getFileName().toString().equals("logs")) continue; - if (p.getFileName().toString().equals("data")) continue; - - if (Files.isRegularFile(p)) { - hashes.put(p.getFileName().toString(), calculateSHA256(p)); - } else if (Files.isDirectory(p)) { - scanDirectory(p, p.getFileName().toString(), hashes); - } - } - } - return hashes; - } - - private static void scanDirectory(Path dir, String basePath, Map hashes) throws Exception { - try (DirectoryStream stream = Files.newDirectoryStream(dir)) { - for (Path p : stream) { - String relPath = basePath + "/" + p.getFileName().toString(); - if (Files.isRegularFile(p)) { - hashes.put(relPath, calculateSHA256(p)); - } else if (Files.isDirectory(p)) { - scanDirectory(p, relPath, hashes); - } - } - } - } - - private static String calculateSHA256(Path file) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - try (InputStream is = Files.newInputStream(file)) { - byte[] buf = new byte[BUFFER_SIZE]; - int len; - while ((len = is.read(buf)) != -1) { - md.update(buf, 0, len); - } - } - StringBuilder sb = new StringBuilder(); - for (byte b : md.digest()) sb.append(String.format("%02x", b)); - return "sha256:" + sb.toString(); - } - - private static Map getLauncherDiff(Map localFiles) throws Exception { - // Создаем JSON {filename: hash, ...} - StringBuilder json = new StringBuilder("{"); - int i = 0; - for (Map.Entry e : localFiles.entrySet()) { - if (i > 0) json.append(","); - json.append("\"").append(e.getKey()).append("\":\"").append(e.getValue()).append("\""); - i++; - } - json.append("}"); - - URL url = new URL(BASE_URL + "/launcher/diff"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - conn.setDoOutput(true); - conn.setRequestProperty("Content-Type", "application/json"); - - try (OutputStream os = conn.getOutputStream()) { - os.write(json.toString().getBytes("UTF-8")); - } - - if (conn.getResponseCode() != 200) { - throw new IOException("Diff запрос вернул: " + conn.getResponseCode()); - } - - StringBuilder sb = new StringBuilder(); - try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { - String line; - while ((line = br.readLine()) != null) sb.append(line); - } - - return parseDiffJson(sb.toString()); - } - - @SuppressWarnings("unchecked") - private static Map parseDiffJson(String json) { - Map result = new HashMap<>(); - List> toDownload = new ArrayList<>(); - List toDelete = new ArrayList<>(); - - String[] parts = json.split(","); - for (String part : parts) { - part = part.trim(); - if (part.contains("\"version\"")) { - result.put("version", extractJsonValue(part)); - } else if (part.contains("\"to_download\"")) { - // Парсим файлы - } else if (part.contains("\"to_delete\"")) { - // Парсим удаляемые - } else if (part.contains("\"path\"") && part.contains("\"size\"")) { - Map file = new HashMap<>(); - if (part.contains("\"hash\"")) file.put("hash", "need"); - file.put("path", extractJsonValue(part)); - String sizePart = part.split("size")[1].split(",")[0]; - file.put("size", Long.parseLong(extractJsonValue(sizePart.split(":")[1]))); - toDownload.add(file); - } else if (part.startsWith("\"")) { - toDelete.add(extractJsonValue(part)); - } - } - - result.put("to_download", toDownload); - result.put("to_delete", toDelete); - return result; - } - - private static void downloadLauncherFile(String version, String filePath) throws Exception { - String encodedPath = filePath.replace(" ", "%20"); - URL url = new URL(BASE_URL + "/launcher/file/" + version + "/" + encodedPath); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - - if (conn.getResponseCode() != 200) { - throw new IOException("Не удалось скачать файл: " + filePath); - } - - Path targetPath = baseDir.resolve(filePath); - Files.createDirectories(targetPath.getParent()); - - try (InputStream in = conn.getInputStream(); - OutputStream out = new FileOutputStream(targetPath.toFile())) { - byte[] buf = new byte[BUFFER_SIZE]; - int len; - long total = 0; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - total += len; - } - } - } - - // Fallback на JAR обновление (без ZIP, чтобы избежать блокировки JRE) - private static void downloadUpdateLegacy(String newVersion) throws Exception { - log("Пропускаем ZIP обновление (может заблокировать JRE)..."); - log("Используем JAR обновление..."); - downloadUpdateJar(newVersion); - } - - private static void downloadUpdateJar(String newVersion) throws Exception { URL url = new URL(BASE_URL + "/launcher/download/jar"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); - + if (conn.getResponseCode() == 200) { Path jarFile = getLauncherJar(); // Скачиваем сразу в целевой файл (без tmp) try (InputStream in = conn.getInputStream(); OutputStream out = new FileOutputStream(jarFile.toFile())) { - byte[] buf = new byte[BUFFER_SIZE]; + byte[] buf = new byte[8192]; int len; long total = 0; while ((len = in.read(buf)) > 0) { @@ -526,14 +137,15 @@ public class Bootstrap { log("JAR скачан: " + Files.size(jarFile) + " bytes"); // Сохраняем версию в файл + Path versionFile = getVersionFile(); try { - Files.writeString(VERSION_FILE, newVersion); - log("Версия сохранена в " + VERSION_FILE); + Files.writeString(versionFile, newVersion); + log("Версия сохранена в " + versionFile); } catch (Exception e) { log("Ошибка сохранения версии: " + e.getMessage()); } - log("Обновлено до v" + newVersion + " (JAR метод)"); + log("Обновлено до v" + newVersion); } else { throw new IOException("Сервер вернул код: " + conn.getResponseCode()); } @@ -542,7 +154,7 @@ public class Bootstrap { private static void launchJFX() throws Exception { Path javaBin = findJava(); Path jarPath = getLauncherJar(); - + log("Запуск JFX режима..."); log("Java: " + javaBin); log("JAR: " + jarPath); @@ -585,7 +197,7 @@ public class Bootstrap { private static void launchCLI() throws Exception { Path javaBin = findJava(); Path jarPath = getLauncherJar(); - + log("Запуск CLI режима..."); log("Java: " + javaBin); log("JAR: " + jarPath);