From dcd5e91ebe6da9a3e309d704a8f56df101e87098 Mon Sep 17 00:00:00 2001 From: SashegDev Date: Sat, 15 Aug 2026 07:29:43 +0000 Subject: [PATCH] =?UTF-8?q?v1.0.16.2=20=E2=80=94=20fix=20NeoForge/Forge=20?= =?UTF-8?q?install=20(bundled=20JRE=20for=20installers,=20visible=20progre?= =?UTF-8?q?ss=20in=20JFX=20UI,=20skip=20existing=20vanilla=20files),=20hid?= =?UTF-8?q?e=20zernmc-cli.exe=20from=20users?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- launcher/launcher/pom.xml | 11 +-- .../minecraft/installer/FabricInstaller.java | 35 ++++----- .../minecraft/installer/ForgeInstaller.java | 55 +++++++------- .../installer/ModLoaderInstaller.java | 55 +++++++------- .../installer/NeoForgeInstaller.java | 55 +++++++------- .../minecraft/installer/VersionInstaller.java | 73 ++++++++++++++----- .../zernmc/launcher/ui/jfx/JFXBridge.java | 20 +++++ .../zernmc/launcher/ui/jfx/JFXLauncher.java | 13 ++++ .../zernmc/launcher/utils/JavaResolver.java | 42 +++++++++++ .../zernmc/launcher/utils/LauncherLogger.java | 7 ++ launcher/pom.xml | 2 +- server/main.py | 3 + 12 files changed, 246 insertions(+), 125 deletions(-) create mode 100644 launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/JavaResolver.java diff --git a/launcher/launcher/pom.xml b/launcher/launcher/pom.xml index c474757..71df809 100644 --- a/launcher/launcher/pom.xml +++ b/launcher/launcher/pom.xml @@ -159,6 +159,7 @@ gui false me.sashegdev.zernmc.launcher.Bootstrap + --jfx lib/jre21 21 @@ -283,20 +284,16 @@ ZernMC Launcher Files: -- zernmc.exe - Main launcher with GUI (no console window) -- zernmc-cli.exe - CLI version for servers/advanced users (with console) +- zernmc.exe - Main launcher with GUI -How to use GUI: +How to use: Just run zernmc.exe - -How to use CLI: - Run from command line: zernmc-cli.exe --cli diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/FabricInstaller.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/FabricInstaller.java index dc7014b..ec5548c 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/FabricInstaller.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/FabricInstaller.java @@ -1,8 +1,9 @@ package me.sashegdev.zernmc.launcher.minecraft.installer; import me.sashegdev.zernmc.launcher.minecraft.Instance; +import me.sashegdev.zernmc.launcher.utils.JavaResolver; +import me.sashegdev.zernmc.launcher.utils.LauncherLogger; import me.sashegdev.zernmc.launcher.utils.ProgressBar; -import me.sashegdev.zernmc.launcher.utils.ZAnsi; import me.sashegdev.zernmc.launcher.utils.ZHttpClient; import java.io.IOException; @@ -19,7 +20,7 @@ public class FabricInstaller { } public boolean install(String minecraftVersion, String loaderVersion) throws Exception { - System.out.println(ZAnsi.cyan("Installing Fabric " + loaderVersion + " for Minecraft " + minecraftVersion)); + LauncherLogger.info("Installing Fabric " + loaderVersion + " for Minecraft " + minecraftVersion); Path instancePath = instance.getPath(); cleanOldFabricLoaders(); @@ -27,7 +28,7 @@ public class FabricInstaller { VersionInstaller versionInstaller = new VersionInstaller(instancePath); String assetIndex = versionInstaller.install(minecraftVersion); - System.out.println(ZAnsi.green("Asset index obtained: " + assetIndex)); + LauncherLogger.info("Asset index obtained: " + assetIndex); instance.setAssetIndex(assetIndex); instance.setMinecraftVersion(minecraftVersion); @@ -46,12 +47,12 @@ public class FabricInstaller { preDownloadFabricLibraries(minecraftVersion, loaderVersion); - System.out.println(ZAnsi.cyan("Running Fabric Installer...")); + LauncherLogger.info("Running Fabric Installer..."); String fabricVersionId = "fabric-loader-" + loaderVersion + "-" + minecraftVersion; ProcessBuilder pb = new ProcessBuilder( - "java", "-jar", installerJar.toAbsolutePath().toString(), + JavaResolver.getJavaExecutable(), "-jar", installerJar.toAbsolutePath().toString(), "client", "-dir", instancePath.toAbsolutePath().toString(), "-mcversion", minecraftVersion, @@ -66,20 +67,20 @@ public class FabricInstaller { boolean finished = process.waitFor(10, TimeUnit.MINUTES); if (!finished) { process.destroyForcibly(); - System.out.println(ZAnsi.brightRed("Fabric Installer timed out after 10 minutes")); + LauncherLogger.error("Fabric Installer timed out after 10 minutes"); return false; } int exitCode = process.exitValue(); if (exitCode != 0) { - System.out.println(ZAnsi.brightRed("Fabric Installer failed (code " + exitCode + ")")); + LauncherLogger.error("Fabric Installer failed (code " + exitCode + ")"); return false; } Path fabricVersionDir = instancePath.resolve("versions").resolve(fabricVersionId); if (Files.exists(fabricVersionDir)) { - System.out.println(ZAnsi.brightGreen("Fabric installed successfully!")); + LauncherLogger.info("Fabric installed successfully!"); instance.setLoaderType("fabric"); instance.setLoaderVersion(loaderVersion); @@ -91,7 +92,7 @@ public class FabricInstaller { return true; } else { - System.out.println(ZAnsi.brightRed("Fabric Installer ran, but version not found.")); + LauncherLogger.error("Fabric Installer ran, but version not found."); return false; } } @@ -100,7 +101,7 @@ public class FabricInstaller { try { ZHttpClient.downloadFile(url, target); } catch (Exception e) { - System.out.println(ZAnsi.yellow("Failed to download Fabric Installer: " + e.getMessage())); + LauncherLogger.warn("Failed to download Fabric Installer: " + e.getMessage()); throw e; } } @@ -132,7 +133,7 @@ public class FabricInstaller { ZHttpClient.repairLibrariesFromJson(loaderJson, instance.getPath()); } catch (Exception e) { - System.out.println(ZAnsi.yellow("Pre-download of Fabric libraries incomplete: " + e.getMessage())); + LauncherLogger.warn("Pre-download of Fabric libraries incomplete: " + e.getMessage()); } } @@ -140,7 +141,7 @@ public class FabricInstaller { Path versionJson = fabricVersionDir.resolve(fabricVersionDir.getFileName() + ".json"); if (!Files.exists(versionJson)) { - System.out.println(ZAnsi.yellow("Version JSON file not found: " + versionJson)); + LauncherLogger.warn("Version JSON file not found: " + versionJson); return; } @@ -148,7 +149,7 @@ public class FabricInstaller { // Check and fix asset index if (!content.contains("\"assets\":\"" + assetIndex + "\"")) { - System.out.println(ZAnsi.yellow("Fixing asset index in version JSON file...")); + LauncherLogger.warn("Fixing asset index in version JSON file..."); // Replace assets with correct value content = content.replaceAll("\"assets\":\\s*\"[^\"]*\"", "\"assets\": \"" + assetIndex + "\""); @@ -159,9 +160,9 @@ public class FabricInstaller { } Files.writeString(versionJson, content); - System.out.println(ZAnsi.green("Asset index fixed to: " + assetIndex)); + LauncherLogger.info("Asset index fixed to: " + assetIndex); } else { - System.out.println(ZAnsi.green("Asset index in version JSON is correct: " + assetIndex)); + LauncherLogger.info("Asset index in version JSON is correct: " + assetIndex); } } @@ -169,7 +170,7 @@ public class FabricInstaller { Path librariesDir = instance.getPath().resolve("libraries/net/fabricmc/fabric-loader"); if (!Files.exists(librariesDir)) return; - System.out.println(ZAnsi.yellow("Cleaning old Fabric Loader versions...")); + LauncherLogger.warn("Cleaning old Fabric Loader versions..."); try (var stream = Files.walk(librariesDir)) { stream.filter(Files::isDirectory) @@ -195,7 +196,7 @@ public class FabricInstaller { int end = xml.indexOf("", start); return xml.substring(start, end).trim(); } catch (Exception e) { - System.out.println(ZAnsi.yellow("Error getting Fabric Installer version: " + e.getMessage())); + LauncherLogger.warn("Error getting Fabric Installer version: " + e.getMessage()); throw new Exception("Failed to get Fabric Installer version", e); } } diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java index 931c3c7..b6ff086 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/ForgeInstaller.java @@ -1,8 +1,9 @@ package me.sashegdev.zernmc.launcher.minecraft.installer; import me.sashegdev.zernmc.launcher.minecraft.Instance; +import me.sashegdev.zernmc.launcher.utils.JavaResolver; +import me.sashegdev.zernmc.launcher.utils.LauncherLogger; import me.sashegdev.zernmc.launcher.utils.ProgressBar; -import me.sashegdev.zernmc.launcher.utils.ZAnsi; import me.sashegdev.zernmc.launcher.utils.ZHttpClient; import java.io.*; import java.nio.file.Files; @@ -19,16 +20,16 @@ public class ForgeInstaller { } public boolean install(String mcVersion, String forgeVersion) throws Exception { - System.out.println(ZAnsi.cyan("Installing Forge " + forgeVersion + " for Minecraft " + mcVersion)); + LauncherLogger.info("Installing Forge " + forgeVersion + " for Minecraft " + mcVersion); // Step 1: Install vanilla and get real assetIndex - System.out.println(ZAnsi.cyan("Installing base Minecraft version " + mcVersion + "...")); + LauncherLogger.info("Installing base Minecraft version " + mcVersion + "..."); VersionInstaller vanillaInstaller = new VersionInstaller(instance.getPath()); String assetIndex = vanillaInstaller.install(mcVersion); if (assetIndex == null || assetIndex.isEmpty()) { - System.out.println(ZAnsi.brightRed("Failed to install base Minecraft version")); + LauncherLogger.error("Failed to install base Minecraft version"); return false; } @@ -44,12 +45,12 @@ public class ForgeInstaller { Path installerJar = instance.getPath().resolve("forge-installer.jar"); - System.out.println(ZAnsi.cyan("Downloading Forge Installer...")); + LauncherLogger.info("Downloading Forge Installer..."); downloadFileWithProgress(installerUrl, installerJar); // Step 4: Run Forge Installer and show its output - System.out.println(ZAnsi.cyan("Running Forge Installer...")); - System.out.println(ZAnsi.yellow("This may take a few minutes. Please wait...\n")); + LauncherLogger.info("Running Forge Installer..."); + LauncherLogger.info("This may take a few minutes. Please wait..."); boolean success = runForgeInstaller(installerJar); @@ -59,10 +60,10 @@ public class ForgeInstaller { try { downloadMissingLibraries(mcVersion, forgeVersion); } catch (Exception e) { - System.out.println(ZAnsi.yellow("Warning: could not download some libraries: " + e.getMessage())); + LauncherLogger.warn("Warning: could not download some libraries: " + e.getMessage()); } - System.out.println(ZAnsi.brightGreen("\nForge " + forgeVersion + " installed successfully!")); + LauncherLogger.info("\nForge " + forgeVersion + " installed successfully!"); instance.setMinecraftVersion(mcVersion); instance.setLoaderType("forge"); instance.setLoaderVersion(forgeVersion); @@ -71,7 +72,7 @@ public class ForgeInstaller { Files.deleteIfExists(installerJar); return true; } else { - System.out.println(ZAnsi.brightRed("\nError installing Forge!")); + LauncherLogger.error("\nError installing Forge!"); return false; } } @@ -87,7 +88,7 @@ public class ForgeInstaller { } """; Files.writeString(profilePath, minimalProfile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - System.out.println(ZAnsi.yellow("Created launcher_profiles.json")); + LauncherLogger.info("Created launcher_profiles.json"); } private void downloadFileWithProgress(String url, Path target) throws Exception { @@ -102,10 +103,10 @@ public class ForgeInstaller { int attempt = 1; while (attempt <= maxRetries) { - System.out.println(ZAnsi.cyan("Attempt " + attempt + " of " + maxRetries)); + LauncherLogger.info("Attempt " + attempt + " of " + maxRetries); ProcessBuilder pb = new ProcessBuilder( - "java", + JavaResolver.getJavaExecutable(), "-jar", installerJar.toAbsolutePath().toString(), "--installClient", @@ -131,18 +132,18 @@ public class ForgeInstaller { // Format Forge Installer output if (line.contains("Downloading") || line.contains("Extracting")) { - System.out.println(ZAnsi.blue(" -> " + line)); + LauncherLogger.info(" -> " + line); } else if (line.contains("SUCCESS") || line.contains("successfully")) { - System.out.println(ZAnsi.brightGreen(" + " + line)); + LauncherLogger.info(" + " + line); } else if (line.contains("WARNING") || line.contains("warning")) { - System.out.println(ZAnsi.yellow(" ! " + line)); + LauncherLogger.warn(" ! " + line); } else if (line.contains("ERROR") || line.contains("error") || line.contains("failed") || line.contains("timed out")) { - System.out.println(ZAnsi.brightRed(" X " + line)); + LauncherLogger.error(" X " + line); if (line.contains("timed out") || line.contains("failed to download")) { hasErrors = true; } } else if (!line.isBlank()) { - System.out.println(" " + line); + LauncherLogger.info(" " + line); } } } @@ -154,7 +155,7 @@ public class ForgeInstaller { } else { if (!process.waitFor(10, TimeUnit.MINUTES)) { process.destroyForcibly(); - System.out.println(ZAnsi.brightRed("Forge Installer timed out after 10 minutes")); + LauncherLogger.error("Forge Installer timed out after 10 minutes"); if (attempt < maxRetries) { attempt++; Thread.sleep(5000); @@ -172,7 +173,7 @@ public class ForgeInstaller { // If error and not last attempt if (attempt < maxRetries) { - System.out.println(ZAnsi.yellow("Install error. Retrying in 5 seconds...")); + LauncherLogger.warn("Install error. Retrying in 5 seconds..."); Thread.sleep(5000); // Clean temp files before retry @@ -188,15 +189,15 @@ public class ForgeInstaller { } } } else { - System.out.println(ZAnsi.brightRed("Forge Installer exited with error code: " + exitCode)); + LauncherLogger.error("Forge Installer exited with error code: " + exitCode); // Show possible solution if (output.toString().contains("timed out")) { - System.out.println(ZAnsi.yellow("\nPossible solutions:")); - System.out.println(ZAnsi.yellow("1. Check your internet connection")); - System.out.println(ZAnsi.yellow("2. Run the launcher as administrator")); - System.out.println(ZAnsi.yellow("3. Temporarily disable antivirus/firewall")); - System.out.println(ZAnsi.yellow("4. Try installing a different Forge version")); + LauncherLogger.warn("\nPossible solutions:"); + LauncherLogger.warn("1. Check your internet connection"); + LauncherLogger.warn("2. Run the launcher as administrator"); + LauncherLogger.warn("3. Temporarily disable antivirus/firewall"); + LauncherLogger.warn("4. Try installing a different Forge version"); } } @@ -207,7 +208,7 @@ public class ForgeInstaller { } private void downloadMissingLibraries(String mcVersion, String forgeVersion) throws Exception { - System.out.println(ZAnsi.cyan("Checking and downloading missing libraries...")); + LauncherLogger.info("Checking and downloading missing libraries..."); ZHttpClient.repairMissingLibraries(instance.getPath()); } } \ No newline at end of file diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/ModLoaderInstaller.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/ModLoaderInstaller.java index dcbb3ae..08db58e 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/ModLoaderInstaller.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/ModLoaderInstaller.java @@ -1,7 +1,8 @@ package me.sashegdev.zernmc.launcher.minecraft.installer; import me.sashegdev.zernmc.launcher.minecraft.Instance; -import me.sashegdev.zernmc.launcher.utils.ZAnsi; +import me.sashegdev.zernmc.launcher.utils.JavaResolver; +import me.sashegdev.zernmc.launcher.utils.LauncherLogger; import me.sashegdev.zernmc.launcher.utils.ZHttpClient; import java.io.*; @@ -42,14 +43,14 @@ public class ModLoaderInstaller { } public boolean install(String mcVersion, String loaderVersion, LoaderType type) throws Exception { - System.out.println(ZAnsi.cyan("Installing " + type.loaderName + " " + loaderVersion + " for Minecraft " + mcVersion)); + LauncherLogger.info("Installing " + type.loaderName + " " + loaderVersion + " for Minecraft " + mcVersion); - System.out.println(ZAnsi.cyan("Installing base Minecraft version " + mcVersion + "...")); + LauncherLogger.info("Installing base Minecraft version " + mcVersion + "..."); VersionInstaller vanillaInstaller = new VersionInstaller(instance.getPath()); String assetIndex = vanillaInstaller.install(mcVersion); if (assetIndex == null || assetIndex.isEmpty()) { - System.out.println(ZAnsi.brightRed("Failed to install base Minecraft version")); + LauncherLogger.error("Failed to install base Minecraft version"); return false; } @@ -60,11 +61,11 @@ public class ModLoaderInstaller { String jarName = type.loaderName + "-installer.jar"; Path installerJar = instance.getPath().resolve(jarName); - System.out.println(ZAnsi.cyan("Downloading " + type.loaderName + " Installer...")); + LauncherLogger.info("Downloading " + type.loaderName + " Installer..."); ZHttpClient.downloadFileWithSmartProxy(installerUrl, installerJar); - System.out.println(ZAnsi.cyan("Running " + type.loaderName + " Installer...")); - System.out.println(ZAnsi.yellow("This may take a few minutes. Please wait...\n")); + LauncherLogger.info("Running " + type.loaderName + " Installer..."); + LauncherLogger.info("This may take a few minutes. Please wait..."); boolean success = runInstaller(installerJar, type); @@ -72,10 +73,10 @@ public class ModLoaderInstaller { try { downloadMissingLibraries(type); } catch (Exception e) { - System.out.println(ZAnsi.yellow("Warning: could not download some libraries: " + e.getMessage())); + LauncherLogger.warn("Warning: could not download some libraries: " + e.getMessage()); } - System.out.println(ZAnsi.brightGreen("\n" + type.loaderName + " " + loaderVersion + " installed successfully!")); + LauncherLogger.info("\n" + type.loaderName + " " + loaderVersion + " installed successfully!"); instance.setMinecraftVersion(mcVersion); instance.setLoaderType(type.loaderName); instance.setLoaderVersion(loaderVersion); @@ -83,7 +84,7 @@ public class ModLoaderInstaller { Files.deleteIfExists(installerJar); return true; } else { - System.out.println(ZAnsi.brightRed("\nError installing " + type.loaderName + "!")); + LauncherLogger.error("\nError installing " + type.loaderName + "!"); return false; } } @@ -112,7 +113,7 @@ public class ModLoaderInstaller { } """; Files.writeString(profilePath, minimalProfile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - System.out.println(ZAnsi.yellow("Created launcher_profiles.json")); + LauncherLogger.info("Created launcher_profiles.json"); } private boolean runInstaller(Path installerJar, LoaderType type) throws IOException, InterruptedException { @@ -120,10 +121,10 @@ public class ModLoaderInstaller { int attempt = 1; while (attempt <= maxRetries) { - System.out.println(ZAnsi.cyan("Attempt " + attempt + " of " + maxRetries)); + LauncherLogger.info("Attempt " + attempt + " of " + maxRetries); ProcessBuilder pb = new ProcessBuilder( - "java", + JavaResolver.getJavaExecutable(), "-jar", installerJar.toAbsolutePath().toString(), "--installClient", @@ -145,18 +146,18 @@ public class ModLoaderInstaller { output.append(line).append("\n"); if (line.contains("Downloading") || line.contains("Extracting")) { - System.out.println(ZAnsi.blue(" -> " + line)); + LauncherLogger.info(" -> " + line); } else if (line.contains("SUCCESS") || line.contains("successfully")) { - System.out.println(ZAnsi.brightGreen(" + " + line)); + LauncherLogger.info(" + " + line); } else if (line.contains("WARNING") || line.contains("warning")) { - System.out.println(ZAnsi.yellow(" ! " + line)); + LauncherLogger.warn(" ! " + line); } else if (line.contains("ERROR") || line.contains("error") || line.contains("failed") || line.contains("timed out")) { - System.out.println(ZAnsi.brightRed(" X " + line)); + LauncherLogger.error(" X " + line); if (line.contains("timed out") || line.contains("failed to download")) { hasErrors = true; } } else if (!line.isBlank()) { - System.out.println(" " + line); + LauncherLogger.info(" " + line); } } } @@ -168,7 +169,7 @@ public class ModLoaderInstaller { } else { if (!process.waitFor(10, TimeUnit.MINUTES)) { process.destroyForcibly(); - System.out.println(ZAnsi.brightRed(type.loaderName + " Installer timed out after 10 minutes")); + LauncherLogger.error(type.loaderName + " Installer timed out after 10 minutes"); if (attempt < maxRetries) { attempt++; Thread.sleep(5000); @@ -184,7 +185,7 @@ public class ModLoaderInstaller { } if (attempt < maxRetries) { - System.out.println(ZAnsi.yellow("Install error. Retrying in 5 seconds...")); + LauncherLogger.warn("Install error. Retrying in 5 seconds..."); Thread.sleep(5000); Path librariesDir = instance.getPath().resolve("libraries"); @@ -198,14 +199,14 @@ public class ModLoaderInstaller { } } } else { - System.out.println(ZAnsi.brightRed(type.loaderName + " Installer exited with error code: " + exitCode)); + LauncherLogger.error(type.loaderName + " Installer exited with error code: " + exitCode); if (output.toString().contains("timed out")) { - System.out.println(ZAnsi.yellow("\nPossible solutions:")); - System.out.println(ZAnsi.yellow("1. Check your internet connection")); - System.out.println(ZAnsi.yellow("2. Run the launcher as administrator")); - System.out.println(ZAnsi.yellow("3. Temporarily disable antivirus/firewall")); - System.out.println(ZAnsi.yellow("4. Try installing a different version")); + LauncherLogger.warn("\nPossible solutions:"); + LauncherLogger.warn("1. Check your internet connection"); + LauncherLogger.warn("2. Run the launcher as administrator"); + LauncherLogger.warn("3. Temporarily disable antivirus/firewall"); + LauncherLogger.warn("4. Try installing a different version"); } } @@ -216,7 +217,7 @@ public class ModLoaderInstaller { } private void downloadMissingLibraries(LoaderType type) throws Exception { - System.out.println(ZAnsi.cyan("Checking and downloading missing libraries...")); + LauncherLogger.info("Checking and downloading missing libraries..."); ZHttpClient.repairMissingLibraries(instance.getPath()); } } diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/NeoForgeInstaller.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/NeoForgeInstaller.java index ccf5836..183c2f0 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/NeoForgeInstaller.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/NeoForgeInstaller.java @@ -1,8 +1,9 @@ package me.sashegdev.zernmc.launcher.minecraft.installer; import me.sashegdev.zernmc.launcher.minecraft.Instance; +import me.sashegdev.zernmc.launcher.utils.JavaResolver; +import me.sashegdev.zernmc.launcher.utils.LauncherLogger; import me.sashegdev.zernmc.launcher.utils.ProgressBar; -import me.sashegdev.zernmc.launcher.utils.ZAnsi; import me.sashegdev.zernmc.launcher.utils.ZHttpClient; import java.io.*; @@ -20,14 +21,14 @@ public class NeoForgeInstaller { } public boolean install(String mcVersion, String neoForgeVersion) throws Exception { - System.out.println(ZAnsi.cyan("Installing NeoForge " + neoForgeVersion + " for Minecraft " + mcVersion)); + LauncherLogger.info("Installing NeoForge " + neoForgeVersion + " for Minecraft " + mcVersion); - System.out.println(ZAnsi.cyan("Installing base Minecraft version " + mcVersion + "...")); + LauncherLogger.info("Installing base Minecraft version " + mcVersion + "..."); VersionInstaller vanillaInstaller = new VersionInstaller(instance.getPath()); String assetIndex = vanillaInstaller.install(mcVersion); if (assetIndex == null || assetIndex.isEmpty()) { - System.out.println(ZAnsi.brightRed("Failed to install base Minecraft version")); + LauncherLogger.error("Failed to install base Minecraft version"); return false; } @@ -45,11 +46,11 @@ public class NeoForgeInstaller { Path installerJar = instance.getPath().resolve("neoforge-installer.jar"); - System.out.println(ZAnsi.cyan("Downloading NeoForge Installer...")); + LauncherLogger.info("Downloading NeoForge Installer..."); downloadFileWithProgress(installerUrl, installerJar); - System.out.println(ZAnsi.cyan("Running NeoForge Installer...")); - System.out.println(ZAnsi.yellow("This may take a few minutes. Please wait...\n")); + LauncherLogger.info("Running NeoForge Installer..."); + LauncherLogger.info("This may take a few minutes. Please wait..."); boolean success = runNeoForgeInstaller(installerJar); @@ -57,10 +58,10 @@ public class NeoForgeInstaller { try { downloadMissingLibraries(mcVersion, neoForgeVersion, mavenGroup, mavenArtifact); } catch (Exception e) { - System.out.println(ZAnsi.yellow("Warning: could not download some libraries: " + e.getMessage())); + LauncherLogger.warn("Warning: could not download some libraries: " + e.getMessage()); } - System.out.println(ZAnsi.brightGreen("\nNeoForge " + neoForgeVersion + " installed successfully!")); + LauncherLogger.info("\nNeoForge " + neoForgeVersion + " installed successfully!"); instance.setMinecraftVersion(mcVersion); instance.setLoaderType("neoforge"); instance.setLoaderVersion(neoForgeVersion); @@ -68,7 +69,7 @@ public class NeoForgeInstaller { Files.deleteIfExists(installerJar); return true; } else { - System.out.println(ZAnsi.brightRed("\nError installing NeoForge!")); + LauncherLogger.error("\nError installing NeoForge!"); return false; } } @@ -98,7 +99,7 @@ public class NeoForgeInstaller { } """; Files.writeString(profilePath, minimalProfile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - System.out.println(ZAnsi.yellow("Created launcher_profiles.json")); + LauncherLogger.info("Created launcher_profiles.json"); } private void downloadFileWithProgress(String url, Path target) throws Exception { @@ -112,10 +113,10 @@ public class NeoForgeInstaller { int attempt = 1; while (attempt <= maxRetries) { - System.out.println(ZAnsi.cyan("Attempt " + attempt + " of " + maxRetries)); + LauncherLogger.info("Attempt " + attempt + " of " + maxRetries); ProcessBuilder pb = new ProcessBuilder( - "java", + JavaResolver.getJavaExecutable(), "-jar", installerJar.toAbsolutePath().toString(), "--installClient", @@ -137,25 +138,25 @@ public class NeoForgeInstaller { output.append(line).append("\n"); if (line.contains("Downloading") || line.contains("Extracting")) { - System.out.println(ZAnsi.blue(" -> " + line)); + LauncherLogger.info(" -> " + line); } else if (line.contains("SUCCESS") || line.contains("successfully")) { - System.out.println(ZAnsi.brightGreen(" + " + line)); + LauncherLogger.info(" + " + line); } else if (line.contains("WARNING") || line.contains("warning")) { - System.out.println(ZAnsi.yellow(" ! " + line)); + LauncherLogger.warn(" ! " + line); } else if (line.contains("ERROR") || line.contains("error") || line.contains("failed") || line.contains("timed out")) { - System.out.println(ZAnsi.brightRed(" X " + line)); + LauncherLogger.error(" X " + line); if (line.contains("timed out") || line.contains("failed to download")) { hasErrors = true; } } else if (!line.isBlank()) { - System.out.println(" " + line); + LauncherLogger.info(" " + line); } } } if (!process.waitFor(10, TimeUnit.MINUTES)) { process.destroyForcibly(); - System.out.println(ZAnsi.brightRed("NeoForge Installer timed out after 10 minutes")); + LauncherLogger.error("NeoForge Installer timed out after 10 minutes"); return false; } int exitCode = process.exitValue(); @@ -165,7 +166,7 @@ public class NeoForgeInstaller { } if (attempt < maxRetries) { - System.out.println(ZAnsi.yellow("Install error. Retrying in 5 seconds...")); + LauncherLogger.warn("Install error. Retrying in 5 seconds..."); Thread.sleep(5000); Path librariesDir = instance.getPath().resolve("libraries"); @@ -179,14 +180,14 @@ public class NeoForgeInstaller { } } } else { - System.out.println(ZAnsi.brightRed("NeoForge Installer exited with error code: " + exitCode)); + LauncherLogger.error("NeoForge Installer exited with error code: " + exitCode); if (output.toString().contains("timed out")) { - System.out.println(ZAnsi.yellow("\nPossible solutions:")); - System.out.println(ZAnsi.yellow("1. Check your internet connection")); - System.out.println(ZAnsi.yellow("2. Run the launcher as administrator")); - System.out.println(ZAnsi.yellow("3. Temporarily disable antivirus/firewall")); - System.out.println(ZAnsi.yellow("4. Try installing a different NeoForge version")); + LauncherLogger.warn("\nPossible solutions:"); + LauncherLogger.warn("1. Check your internet connection"); + LauncherLogger.warn("2. Run the launcher as administrator"); + LauncherLogger.warn("3. Temporarily disable antivirus/firewall"); + LauncherLogger.warn("4. Try installing a different NeoForge version"); } } @@ -197,7 +198,7 @@ public class NeoForgeInstaller { } private void downloadMissingLibraries(String mcVersion, String neoForgeVersion, String mavenGroup, String mavenArtifact) throws Exception { - System.out.println(ZAnsi.cyan("Checking and downloading missing libraries...")); + LauncherLogger.info("Checking and downloading missing libraries..."); ZHttpClient.repairMissingLibraries(instance.getPath()); } } diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java index 94ddd70..5065cbc 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/minecraft/installer/VersionInstaller.java @@ -1,6 +1,7 @@ package me.sashegdev.zernmc.launcher.minecraft.installer; import me.sashegdev.zernmc.launcher.minecraft.model.MinecraftVersion; +import me.sashegdev.zernmc.launcher.utils.LauncherLogger; import me.sashegdev.zernmc.launcher.utils.ProgressBar; import me.sashegdev.zernmc.launcher.utils.ZAnsi; import me.sashegdev.zernmc.launcher.utils.ZHttpClient; @@ -8,6 +9,7 @@ import org.json.JSONArray; import org.json.JSONObject; import java.io.InputStream; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -56,11 +58,22 @@ public class VersionInstaller { } public String install(String versionId) throws Exception { - System.out.println(ZAnsi.cyan("Full install of Minecraft " + versionId + "...")); + LauncherLogger.info("Full install of Minecraft " + versionId + "..."); ProgressBar.setStage("Version", 0, 5); Path versionDir = minecraftDir.resolve("versions").resolve(versionId); Files.createDirectories(versionDir); + boolean alreadyInstalled = Files.exists(versionDir.resolve(versionId + ".json")) + && Files.exists(versionDir.resolve(versionId + ".jar")); + if (alreadyInstalled) { + LauncherLogger.info("Minecraft " + versionId + " already installed, skipping"); + String existing = Files.readString(versionDir.resolve(versionId + ".json")); + JSONObject existingData = new JSONObject(existing); + return existingData.has("assetIndex") + ? existingData.getJSONObject("assetIndex").getString("id") + : existingData.getString("assets"); + } + ProgressBar.show("Fetching version info", 0, 1, "files"); String versionJson; try { @@ -82,24 +95,27 @@ public class VersionInstaller { // client.jar ProgressBar.show("Downloading client.jar", 0, 1, "files"); - try { - ZHttpClient.downloadFileWithSmartProxy( - versionData.getJSONObject("downloads").getJSONObject("client").getString("url"), - versionDir.resolve(versionId + ".jar")); - } catch (Exception e) { - System.err.println(ZAnsi.red("[VERSION] Failed to download client.jar: " + e.getMessage())); - throw e; + Path clientJar = versionDir.resolve(versionId + ".jar"); + if (!Files.exists(clientJar)) { + try { + ZHttpClient.downloadFileWithSmartProxy( + versionData.getJSONObject("downloads").getJSONObject("client").getString("url"), + clientJar); + } catch (Exception e) { + System.err.println(ZAnsi.red("[VERSION] Failed to download client.jar: " + e.getMessage())); + throw e; + } } ProgressBar.show("Client.jar", 1, 1, "files"); // Libraries ProgressBar.setStage("Libraries", 1, 5); - System.out.println(ZAnsi.cyan("Downloading libraries...")); + LauncherLogger.info("Downloading libraries..."); downloadLibraries(versionData.getJSONArray("libraries")); // Natives ProgressBar.setStage("Natives", 2, 5); - System.out.println(ZAnsi.cyan("Extracting natives...")); + LauncherLogger.info("Extracting natives..."); extractNatives(versionData.getJSONArray("libraries")); String assetIndex; @@ -109,15 +125,15 @@ public class VersionInstaller { assetIndex = versionData.getString("assets"); } - System.out.println(ZAnsi.cyan("Asset index: " + assetIndex)); + LauncherLogger.info("Asset index: " + assetIndex); // Download assets using correct index ProgressBar.setStage("Assets", 3, 5); - System.out.println(ZAnsi.cyan("Downloading assets...")); + LauncherLogger.info("Downloading assets..."); downloadAssets(versionData, assetIndex); ProgressBar.setStage("Done", 4, 5); - System.out.println(ZAnsi.brightGreen("\nMinecraft " + versionId + " fully installed!")); + LauncherLogger.info("\nMinecraft " + versionId + " fully installed!"); return assetIndex; } @@ -190,7 +206,7 @@ public class VersionInstaller { if (extracted > 0) { ProgressBar.finish("Natives extracted (" + extracted + " ok, " + failed + " failed)"); } else { - System.out.println(ZAnsi.yellow("No natives to extract for " + classifier)); + LauncherLogger.warn("No natives to extract for " + classifier); } } @@ -208,6 +224,12 @@ public class VersionInstaller { Path target = minecraftDir.resolve("libraries").resolve(path); Files.createDirectories(target.getParent()); + if (Files.exists(target)) { + count++; + ProgressBar.show("Libraries", count, total, "files"); + continue; + } + try { ZHttpClient.downloadFileWithSmartProxy(url, target); } catch (Exception e) { @@ -229,14 +251,18 @@ public class VersionInstaller { Files.createDirectories(indexesDir); Path indexPath = indexesDir.resolve(assetIndex + ".json"); - System.out.println(ZAnsi.cyan("Downloading asset index (" + assetIndex + ")...")); - ZHttpClient.downloadFileWithSmartProxy(indexUrl, indexPath); + if (!Files.exists(indexPath)) { + LauncherLogger.info("Downloading asset index (" + assetIndex + ")..."); + ZHttpClient.downloadFileWithSmartProxy(indexUrl, indexPath); + } else { + LauncherLogger.info("Asset index already present (" + assetIndex + ")"); + } String jsonContent = Files.readString(indexPath); JSONObject root = new JSONObject(jsonContent); JSONObject objects = root.getJSONObject("objects"); - System.out.println(ZAnsi.cyan("Downloading " + objects.length() + " asset objects (index: " + assetIndex + ")...")); + LauncherLogger.info("Downloading " + objects.length() + " asset objects (index: " + assetIndex + ")..."); int total = objects.length(); int[] success = {0}; @@ -256,6 +282,15 @@ public class VersionInstaller { Files.createDirectories(target.getParent()); CompletableFuture future = CompletableFuture.runAsync(() -> { + try { + if (Files.exists(target) && Files.size(target) > 0) { + synchronized (this) { + success[0]++; + ProgressBar.show("Assets", success[0], total, "files"); + } + return; + } + } catch (IOException ignored) {} boolean downloaded = false; for (int attempt = 1; attempt <= 3; attempt++) { try { @@ -297,8 +332,8 @@ public class VersionInstaller { ProgressBar.finish("Assets downloaded (" + success[0] + " ok, " + failed[0] + " skipped)"); if (failed[0] > 0) { - System.out.println(ZAnsi.yellow("Warning: " + failed[0] + " asset files could not be downloaded.")); - System.out.println(ZAnsi.yellow("The game will launch, but some textures/sounds may be missing.")); + LauncherLogger.warn("Warning: " + failed[0] + " asset files could not be downloaded."); + LauncherLogger.warn("The game will launch, but some textures/sounds may be missing."); } } diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXBridge.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXBridge.java index f797be4..c6f4c31 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXBridge.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXBridge.java @@ -20,6 +20,7 @@ public final class JFXBridge { private static final String JFX_CLASS = "me.sashegdev.zernmc.launcher.ui.jfx.JFXLauncher"; private static volatile java.lang.reflect.Method appendGameLogMethod; + private static volatile java.lang.reflect.Method appendLauncherLogFromLoggerMethod; private static volatile java.lang.reflect.Method setInstallProgressMethod; private static volatile java.lang.reflect.Method setInstallStageMethod; @@ -49,6 +50,25 @@ public final class JFXBridge { } } + /** + * Forward a launcher log line to the JFX UI without re-logging it through + * LauncherLogger. Called from LauncherLogger.write so all logged lines + * (including installer output) become visible in the UI. Safe no-op in CLI mode. + */ + public static void appendLauncherLogFromLogger(String msg) { + if (appendLauncherLogFromLoggerMethod == null) { + appendLauncherLogFromLoggerMethod = method("appendLauncherLogFromLogger", String.class); + } + if (appendLauncherLogFromLoggerMethod == null) { + return; + } + try { + appendLauncherLogFromLoggerMethod.invoke(null, msg); + } catch (Throwable t) { + appendLauncherLogFromLoggerMethod = null; + } + } + /** Forward install progress to the JFX UI. Safe no-op in CLI mode. */ public static void setInstallProgress(String label, int current, int total) { if (setInstallProgressMethod == null) { diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java index ca7ca7d..680fd15 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/ui/jfx/JFXLauncher.java @@ -134,6 +134,19 @@ public class JFXLauncher extends Application { try { consumer.onLog(msg); } catch (Exception ignored) {} } } + + /** + * Adds a line to the UI log buffer without re-logging it through + * LauncherLogger (avoids recursion when called from LauncherLogger.write). + */ + public static void appendLauncherLogFromLogger(String msg) { + synchronized (launcherLogBuffer) { + launcherLogBuffer.append(msg).append("\n"); + } + for (LogConsumer consumer : logConsumers) { + try { consumer.onLog(msg); } catch (Exception ignored) {} + } + } public static void appendGameLog(String msg) { synchronized (gameLogBuffer) { diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/JavaResolver.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/JavaResolver.java new file mode 100644 index 0000000..397fbb3 --- /dev/null +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/JavaResolver.java @@ -0,0 +1,42 @@ +package me.sashegdev.zernmc.launcher.utils; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Resolves the java executable used to run the launcher itself, so child + * processes (Forge/NeoForge/Fabric installers) run on the bundled JRE + * instead of relying on a system java that may not exist or be too old. + * + * Order of preference: + * 1. The JVM currently running the launcher (bundled jre21 in lib/jre21). + * 2. A jre21 next to the working directory (dev layout). + * 3. System PATH lookup (last resort). + */ +public final class JavaResolver { + + private JavaResolver() {} + + public static String getJavaExecutable() { + String os = System.getProperty("os.name", "").toLowerCase(); + String javaExe = os.contains("win") ? "java.exe" : "java"; + + Path fromHome = Paths.get(System.getProperty("java.home"), "bin", javaExe); + if (Files.exists(fromHome)) { + return fromHome.toAbsolutePath().toString(); + } + + Path bundled = Paths.get("").toAbsolutePath().resolve("lib").resolve("jre21").resolve("bin").resolve(javaExe); + if (Files.exists(bundled)) { + return bundled.toString(); + } + + Path devJre = Paths.get("").toAbsolutePath().resolve("jre21").resolve("bin").resolve(javaExe); + if (Files.exists(devJre)) { + return devJre.toString(); + } + + return javaExe; + } +} diff --git a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/LauncherLogger.java b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/LauncherLogger.java index 70013a8..4b70d91 100644 --- a/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/LauncherLogger.java +++ b/launcher/launcher/src/main/java/sashegdev/zernmc/launcher/utils/LauncherLogger.java @@ -1,5 +1,7 @@ package me.sashegdev.zernmc.launcher.utils; +import me.sashegdev.zernmc.launcher.ui.jfx.JFXBridge; + import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; @@ -94,6 +96,11 @@ public class LauncherLogger { // full/hung stdout pipe (e.g. javaw piped to the parent exe) can never // block application threads. offerStdout(line); + + // Forward to the JFX UI log so installer/progress output is visible + // in the GUI. The bridge is a safe no-op when JavaFX is unavailable. + JFXBridge.appendLauncherLogFromLogger(line); + if (t != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); diff --git a/launcher/pom.xml b/launcher/pom.xml index 0ddd088..557c35c 100644 --- a/launcher/pom.xml +++ b/launcher/pom.xml @@ -19,7 +19,7 @@ 1.0.16 - 0 + 2 21 21 UTF-8 diff --git a/server/main.py b/server/main.py index 9ceaa7b..6b2d9ea 100644 --- a/server/main.py +++ b/server/main.py @@ -1347,6 +1347,9 @@ def generate_launcher_builds_meta(): # Skip root-level JARs — only bin/*.jar is needed at runtime if rel_path in ("zernmclauncher.jar", "zernmc-bootstrap.jar"): continue + # CLI exe is not shipped to users + if rel_path == "zernmc-cli.exe": + continue stat = file_path.stat() # Calculate hash