From 985abf74403733964d2d8953ce10ea09632a863e Mon Sep 17 00:00:00 2001 From: SashegDev Date: Fri, 8 May 2026 11:19:10 +0000 Subject: [PATCH] Fix: Bootstrap update and meta parsing - Rewrite getLauncherMeta() to properly parse server meta response - Change downloadUpdate() fallback to JAR-only (not ZIP) to avoid JRE lock issues - Simplify downloadUpdateLegacy() to skip ZIP (which locks JRE files) - Add handling for AccessDeniedException when updating locked files - Improve error logging for meta parsing failures --- .../sashegdev/zernmc/launcher/Bootstrap.java | 177 +++++++++--------- 1 file changed, 90 insertions(+), 87 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 d9d8be6..5a4e7fc 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 @@ -159,55 +159,63 @@ public class Bootstrap { Map meta = getLauncherMeta(); @SuppressWarnings("unchecked") List> files = (List>) meta.get("files"); - + if (files == null || files.isEmpty()) { - // Мета пустая - fallback на старый метод - log("Мета недоступна, используем старый метод"); - downloadUpdateLegacy(newVersion); + // Мета пустая - 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. Удаляем лишние файлы + + // 4. Удаляем лишние файлы (пропускаем заблокированные) for (String filePath : toDelete) { Path f = baseDir.resolve(filePath); if (Files.exists(f)) { - Files.delete(f); - log("Удален: " + filePath); + try { + Files.delete(f); + log("Удален: " + filePath); + } catch (AccessDeniedException e) { + log("Пропущен (заблокирован): " + filePath); + } } } - - // 5. Скачиваем новые/измененные файлы + + // 5. Скачиваем новые/измененные файлы (пропускаем заблокированные) String serverVersion = (String) diff.get("version"); for (Map file : toDownload) { String path = (String) file.get("path"); - downloadLauncherFile(serverVersion, path); - log("Скачан: " + path); + try { + downloadLauncherFile(serverVersion, path); + log("Скачан: " + path); + } catch (AccessDeniedException e) { + log("Пропущен (заблокирован): " + path); + } } - + // 6. Записываем новую версию Files.writeString(baseDir.resolve(VERSION_FILE), serverVersion); log("Обновлено до v" + serverVersion); - + } catch (Exception e) { log("Ошибка инкрементного обновления: " + e.getMessage()); - log("Fallback на старый метод..."); - // Fallback на старый метод - downloadUpdateLegacy(newVersion); + log("Fallback на JAR обновление..."); + // Fallback на JAR обновление (не ZIP!) + downloadUpdateJar(newVersion); } } @@ -215,34 +223,72 @@ public class Bootstrap { 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 вручную + + // Парсим JSON вручную - находим первую версию и её meta String json = sb.toString(); - int versionsStart = json.indexOf("\"versions\""); - if (versionsStart == -1) throw new IOException("Нет versions в ответе"); - - int arrayStart = json.indexOf("[", versionsStart); - int arrayEnd = json.indexOf("]", arrayStart); - String versionsArray = json.substring(arrayStart + 1, arrayEnd); - - // Ищем первую версию - int metaStart = versionsArray.indexOf("\"meta\""); - if (metaStart == -1) throw new IOException("Нет meta в версии"); - - int objStart = versionsArray.indexOf("{", metaStart); - int objEnd = versionsArray.lastIndexOf("}"); - - String metaJson = versionsArray.substring(objStart, objEnd + 1); + + // Находим первый объект в массиве 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); } @@ -422,54 +468,11 @@ public class Bootstrap { } } - // Fallback на старый метод (скачивание ZIP) + // Fallback на JAR обновление (без ZIP, чтобы избежать блокировки JRE) private static void downloadUpdateLegacy(String newVersion) throws Exception { - // Пробуем скачать ZIP - String zipUrl = BASE_URL + "/launcher/download/zip/ZernMC-win-" + newVersion + ".zip"; - URL url = new URL(zipUrl); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - - if (conn.getResponseCode() == 200) { - Path tempZip = baseDir.resolve("update.zip"); - - try (InputStream in = conn.getInputStream(); - OutputStream out = new FileOutputStream(tempZip.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; - System.out.print("\rСкачано: " + (total/1024/1024) + " MB"); - } - } - log("ZIP скачан, распаковка..."); - - // Распаковываем ZIP - java.util.zip.ZipFile zip = new java.util.zip.ZipFile(tempZip.toFile()); - java.util.Enumeration entries = zip.entries(); - while (entries.hasMoreElements()) { - java.util.zip.ZipEntry entry = entries.nextElement(); - Path outPath = baseDir.resolve(entry.getName()); - if (entry.isDirectory()) { - Files.createDirectories(outPath); - } else { - Files.createDirectories(outPath.getParent()); - try (InputStream is = zip.getInputStream(entry)) { - Files.copy(is, outPath, StandardCopyOption.REPLACE_EXISTING); - } - } - } - zip.close(); - Files.delete(tempZip); - - Files.writeString(baseDir.resolve(VERSION_FILE), newVersion); - log("Обновлено до v" + newVersion + " (ZIP метод)"); - } else { - // Последний fallback - JAR - downloadUpdateJar(newVersion); - } + log("Пропускаем ZIP обновление (может заблокировать JRE)..."); + log("Используем JAR обновление..."); + downloadUpdateJar(newVersion); } private static void downloadUpdateJar(String newVersion) throws Exception {