import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.security.MessageDigest; import java.time.Duration; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Mise à jour automatique des mods NeoGames : compare le dossier mods avec la liste du serveur (SHA-256) et ne télécharge que ce qui change. * Lancement : java Updater.java [dossier-mods] [url-de-base] (aucune installation, juste Java). Hors ligne : ne bloque jamais le jeu. */ public class Updater { static final String DEFAULT_URL = "https://git.neofloodgames.fr/NeoFloodGames/oneblock-release/raw/branch/main/"; public static void main(String[] a) { try { run(a); } catch (Exception e) { System.out.println("[NeoGames] Mise à jour impossible (" + e + ") : on lance avec les mods déjà présents."); } } static void run(String[] a) throws Exception { Path mods = Path.of(a.length > 0 ? a[0] : "mods"); String base = a.length > 1 ? a[1] : System.getenv().getOrDefault("NEOGAMES_URL", DEFAULT_URL); if (!base.endsWith("/")) base += "/"; Files.createDirectories(mods); HttpClient http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(6)).followRedirects(HttpClient.Redirect.NORMAL).build(); String manifest = new String(get(http, base + "manifest.txt"), StandardCharsets.UTF_8); List files = new ArrayList<>(); List managed = new ArrayList<>(); for (String line : manifest.split("\\R")) { line = line.trim(); if (line.isEmpty()) continue; if (line.startsWith("#managed ")) { for (String p : line.substring(9).split("\\s+")) managed.add(p); continue; } if (line.startsWith("#")) continue; String[] p = line.split("\\s+", 3); // sha256 taille nom if (p.length == 3 && p[2].endsWith(".jar") && !p[2].contains("/") && !p[2].contains("\\")) files.add(p); } if (files.isEmpty()) throw new IOException("liste vide"); Set keep = new HashSet<>(); int updated = 0; for (String[] f : files) { keep.add(f[2]); Path target = mods.resolve(f[2]); if (Files.exists(target) && sha256(Files.readAllBytes(target)).equals(f[0])) continue; System.out.println("[NeoGames] Téléchargement de " + f[2] + " (" + Long.parseLong(f[1]) / 1024 + " Ko)..."); byte[] data = get(http, base + f[2]); if (!sha256(data).equals(f[0])) throw new IOException("fichier corrompu : " + f[2]); Path tmp = mods.resolve(f[2] + ".part"); Files.write(tmp, data); Files.move(tmp, target, StandardCopyOption.REPLACE_EXISTING); updated++; } // anciennes versions des mods gérés (même préfixe, nom différent) : retirées pour éviter les doublons try (var s = Files.list(mods)) { for (Path p : (Iterable) s::iterator) { String n = p.getFileName().toString(); if (!n.endsWith(".jar") || keep.contains(n)) continue; for (String pre : managed) if (n.startsWith(pre)) { Files.deleteIfExists(p); System.out.println("[NeoGames] Ancienne version retirée : " + n); break; } } } System.out.println(updated == 0 ? "[NeoGames] Mods à jour." : "[NeoGames] " + updated + " mod(s) mis à jour."); } static byte[] get(HttpClient http, String url) throws Exception { HttpResponse r = http.send(HttpRequest.newBuilder(URI.create(url)).timeout(Duration.ofSeconds(60)).build(), HttpResponse.BodyHandlers.ofByteArray()); if (r.statusCode() != 200) throw new IOException("HTTP " + r.statusCode() + " sur " + url); return r.body(); } static String sha256(byte[] data) throws Exception { StringBuilder sb = new StringBuilder(); for (byte b : MessageDigest.getInstance("SHA-256").digest(data)) sb.append(String.format("%02x", b)); return sb.toString(); } }