v1.0.16.2 — fix NeoForge/Forge install (bundled JRE for installers, visible progress in JFX UI, skip existing vanilla files), hide zernmc-cli.exe from users

This commit is contained in:
SashegDev
2026-08-15 07:29:43 +00:00
parent 21cd30c1c3
commit a815d685d3
12 changed files with 246 additions and 125 deletions
+4 -7
View File
@@ -159,6 +159,7 @@
<headerType>gui</headerType>
<dontWrapJar>false</dontWrapJar>
<mainClass>me.sashegdev.zernmc.launcher.Bootstrap</mainClass>
<cmdLine>--jfx</cmdLine>
<jre>
<path>lib/jre21</path>
<minVersion>21</minVersion>
@@ -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
</echo>
<!-- Создаём один архив со всем -->
<zip destfile="../../server/builds/ZernMC-win-${revision}.${hotfix}.zip"
basedir="../../server/builds"
includes="zernmc.exe,zernmc-cli.exe,bin/**,assets/**,lib/**,README.txt"
includes="zernmc.exe,bin/**,assets/**,lib/**,README.txt"
excludes="build.version,*.jar"/>
</target>
</configuration>
@@ -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("</latest>", 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);
}
}
@@ -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());
}
}
@@ -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());
}
}
@@ -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());
}
}
@@ -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");
Path clientJar = versionDir.resolve(versionId + ".jar");
if (!Files.exists(clientJar)) {
try {
ZHttpClient.downloadFileWithSmartProxy(
versionData.getJSONObject("downloads").getJSONObject("client").getString("url"),
versionDir.resolve(versionId + ".jar"));
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 + ")..."));
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<Void> 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.");
}
}
@@ -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) {
@@ -135,6 +135,19 @@ public class JFXLauncher extends Application {
}
}
/**
* 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) {
gameLogBuffer.append(msg).append("\n");
@@ -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;
}
}
@@ -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);
+1 -1
View File
@@ -19,7 +19,7 @@
<properties>
<revision>1.0.16</revision>
<hotfix>0</hotfix>
<hotfix>2</hotfix>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+3
View File
@@ -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