fix: inject real version into UI, add timeout to FabricInstaller process

This commit is contained in:
SashegDev
2026-07-30 15:30:32 +00:00
parent 1307d10e81
commit 6024df5093
4 changed files with 27 additions and 50 deletions
@@ -6,20 +6,13 @@ import me.sashegdev.zernmc.launcher.utils.ZAnsi;
import me.sashegdev.zernmc.launcher.utils.ZHttpClient; import me.sashegdev.zernmc.launcher.utils.ZHttpClient;
import java.io.IOException; 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.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration; import java.util.concurrent.TimeUnit;
public class FabricInstaller { public class FabricInstaller {
private final Instance instance; private final Instance instance;
private final HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(15))
.build();
public FabricInstaller(Instance instance) { public FabricInstaller(Instance instance) {
this.instance = instance; this.instance = instance;
@@ -68,7 +61,13 @@ public class FabricInstaller {
pb.redirectError(ProcessBuilder.Redirect.INHERIT); pb.redirectError(ProcessBuilder.Redirect.INHERIT);
Process process = pb.start(); Process process = pb.start();
int exitCode = process.waitFor(); boolean finished = process.waitFor(10, TimeUnit.MINUTES);
if (!finished) {
process.destroyForcibly();
System.out.println(ZAnsi.brightRed("Fabric Installer timed out after 10 minutes"));
return false;
}
int exitCode = process.exitValue();
if (exitCode != 0) { if (exitCode != 0) {
System.out.println(ZAnsi.brightRed("Fabric Installer failed (code " + exitCode + ")")); System.out.println(ZAnsi.brightRed("Fabric Installer failed (code " + exitCode + ")"));
@@ -166,48 +165,11 @@ public class FabricInstaller {
} }
} }
// under refactor - keep
private String downloadString(String url) throws Exception { private String downloadString(String url) throws Exception {
Exception lastException = null; return ZHttpClient.getWithSmartProxy(url);
for (int attempt = 1; attempt <= 3; attempt++) {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(30 * attempt))
.header("User-Agent", "ZernMC-Launcher/1.0")
.GET()
.build();
HttpResponse<String> resp = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() == 200) {
return resp.body();
}
throw new IOException("HTTP " + resp.statusCode());
} catch (Exception e) {
lastException = e;
System.out.println(ZAnsi.yellow("Attempt " + attempt + " failed: " + e.getMessage()));
if (attempt < 3) {
Thread.sleep(1000 * attempt);
}
}
}
throw lastException;
} }
private void downloadFile(String url, Path target) throws Exception { private void downloadFile(String url, Path target) throws Exception {
HttpRequest request = HttpRequest.newBuilder() ZHttpClient.downloadFileWithSmartProxy(url, target);
.uri(URI.create(url))
.timeout(Duration.ofSeconds(60))
.GET()
.build();
HttpResponse<Path> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofFile(target));
if (response.statusCode() != 200) {
throw new IOException("HTTP " + response.statusCode() + " when downloading " + url);
}
} }
} }
@@ -1229,6 +1229,12 @@ public class JFXLauncher extends Application {
log("[UI] Loaded " + content.length + " bytes: " + path); log("[UI] Loaded " + content.length + " bytes: " + path);
String ct = getContentType(path); String ct = getContentType(path);
if (ct.startsWith("text/")) {
String text = new String(content, java.nio.charset.StandardCharsets.UTF_8);
text = text.replace("__LAUNCHER_VERSION__", me.sashegdev.zernmc.launcher.utils.Version.getCurrentVersion());
content = text.getBytes(java.nio.charset.StandardCharsets.UTF_8);
}
exchange.getResponseHeaders().set("Content-Type", ct); exchange.getResponseHeaders().set("Content-Type", ct);
exchange.sendResponseHeaders(200, content.length); exchange.sendResponseHeaders(200, content.length);
exchange.getResponseBody().write(content); exchange.getResponseBody().write(content);
@@ -28,7 +28,7 @@
</div> </div>
<h1 class="login-title" id="login-title" data-i18n="login.title">Sign In</h1> <h1 class="login-title" id="login-title" data-i18n="login.title">Sign In</h1>
<p class="login-subtitle" id="login-subtitle" data-i18n="login.subtitle">Welcome back to ZernMC</p> <p class="login-subtitle" id="login-subtitle" data-i18n="login.subtitle">Welcome back to ZernMC</p>
<span id="version" class="hidden">1.0.12</span> <span id="version" class="hidden">__LAUNCHER_VERSION__</span>
</div> </div>
<form id="login-form" class="login-form"> <form id="login-form" class="login-form">
@@ -81,7 +81,7 @@
</svg> </svg>
<div class="sidebar-brand-text"> <div class="sidebar-brand-text">
<span class="sidebar-brand-name">ZernMC</span> <span class="sidebar-brand-name">ZernMC</span>
<span class="sidebar-brand-ver">v<span id="header-version">1.0.12</span></span> <span class="sidebar-brand-ver">v<span id="header-version">__LAUNCHER_VERSION__</span></span>
</div> </div>
</div> </div>
+9
View File
@@ -0,0 +1,9 @@
TODO / ideas backlog
====================
Java Agent + IPC (low priority, overkill now)
- Заменить прямой ProcessBuilder.start() на прокси-класс (-javaagent или -cp прокси)
- Прокси класс: подключается к localhost RPC, ждёт "launch", потом reflection вызывает реальный main
- Даёт: двустороннюю связь лаунчер-игра, контроль жизненного цикла, crash-репорты из процесса игры,
передачу настроек/токенов на лету, патчинг классов через Instrumentation API
- Для ZernMC сейчас оверхед — но запомнить на будущее