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
This commit is contained in:
SashegDev
2026-05-08 11:19:10 +00:00
parent ec551ab2e3
commit 985abf7440
@@ -161,9 +161,9 @@ public class Bootstrap {
List<Map<String, Object>> files = (List<Map<String, Object>>) meta.get("files"); List<Map<String, Object>> files = (List<Map<String, Object>>) meta.get("files");
if (files == null || files.isEmpty()) { if (files == null || files.isEmpty()) {
// Мета пустая - fallback на старый метод // Мета пустая - fallback на JAR обновление
log("Мета недоступна, используем старый метод"); log("Мета пустая, используем JAR обновление");
downloadUpdateLegacy(newVersion); downloadUpdateJar(newVersion);
return; return;
} }
@@ -182,21 +182,29 @@ public class Bootstrap {
log("К скачиванию: " + toDownload.size() + ", к удалению: " + toDelete.size()); log("К скачиванию: " + toDownload.size() + ", к удалению: " + toDelete.size());
// 4. Удаляем лишние файлы // 4. Удаляем лишние файлы (пропускаем заблокированные)
for (String filePath : toDelete) { for (String filePath : toDelete) {
Path f = baseDir.resolve(filePath); Path f = baseDir.resolve(filePath);
if (Files.exists(f)) { if (Files.exists(f)) {
Files.delete(f); try {
log("Удален: " + filePath); Files.delete(f);
log("Удален: " + filePath);
} catch (AccessDeniedException e) {
log("Пропущен (заблокирован): " + filePath);
}
} }
} }
// 5. Скачиваем новые/измененные файлы // 5. Скачиваем новые/измененные файлы (пропускаем заблокированные)
String serverVersion = (String) diff.get("version"); String serverVersion = (String) diff.get("version");
for (Map<String, Object> file : toDownload) { for (Map<String, Object> file : toDownload) {
String path = (String) file.get("path"); String path = (String) file.get("path");
downloadLauncherFile(serverVersion, path); try {
log("Скачан: " + path); downloadLauncherFile(serverVersion, path);
log("Скачан: " + path);
} catch (AccessDeniedException e) {
log("Пропущен (заблокирован): " + path);
}
} }
// 6. Записываем новую версию // 6. Записываем новую версию
@@ -205,9 +213,9 @@ public class Bootstrap {
} catch (Exception e) { } catch (Exception e) {
log("Ошибка инкрементного обновления: " + e.getMessage()); log("Ошибка инкрементного обновления: " + e.getMessage());
log("Fallback на старый метод..."); log("Fallback на JAR обновление...");
// Fallback на старый метод // Fallback на JAR обновление (не ZIP!)
downloadUpdateLegacy(newVersion); downloadUpdateJar(newVersion);
} }
} }
@@ -226,23 +234,61 @@ public class Bootstrap {
while ((line = br.readLine()) != null) sb.append(line); while ((line = br.readLine()) != null) sb.append(line);
} }
// Парсим JSON вручную // Парсим JSON вручную - находим первую версию и её meta
String json = sb.toString(); String json = sb.toString();
int versionsStart = json.indexOf("\"versions\"");
if (versionsStart == -1) throw new IOException("Нет versions в ответе");
int arrayStart = json.indexOf("[", versionsStart); // Находим первый объект в массиве versions
int arrayEnd = json.indexOf("]", arrayStart); int versionsIdx = json.indexOf("\"versions\"");
String versionsArray = json.substring(arrayStart + 1, arrayEnd); if (versionsIdx == -1) throw new IOException("Нет versions в ответе");
// Ищем первую версию // Находим начало массива
int metaStart = versionsArray.indexOf("\"meta\""); int arrStart = json.indexOf("[", versionsIdx);
if (metaStart == -1) throw new IOException("Нет meta в версии"); if (arrStart == -1) throw new IOException("Нет массива versions");
int objStart = versionsArray.indexOf("{", metaStart); // Находим первый объект версии
int objEnd = versionsArray.lastIndexOf("}"); int verObjStart = json.indexOf("{", arrStart);
if (verObjStart == -1) throw new IOException("Нет объектов версий");
String metaJson = versionsArray.substring(objStart, objEnd + 1); // Находим конец этого объекта (с учётом вложенности)
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); return parseMetaJson(metaJson);
} }
@@ -422,54 +468,11 @@ public class Bootstrap {
} }
} }
// Fallback на старый метод (скачивание ZIP) // Fallback на JAR обновление (без ZIP, чтобы избежать блокировки JRE)
private static void downloadUpdateLegacy(String newVersion) throws Exception { private static void downloadUpdateLegacy(String newVersion) throws Exception {
// Пробуем скачать ZIP log("Пропускаем ZIP обновление (может заблокировать JRE)...");
String zipUrl = BASE_URL + "/launcher/download/zip/ZernMC-win-" + newVersion + ".zip"; log("Используем JAR обновление...");
URL url = new URL(zipUrl); downloadUpdateJar(newVersion);
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<? extends java.util.zip.ZipEntry> 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);
}
} }
private static void downloadUpdateJar(String newVersion) throws Exception { private static void downloadUpdateJar(String newVersion) throws Exception {