使用 Java 11 构建 HMCL (#4078)

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
This commit is contained in:
Glavo 2025-07-25 16:05:09 +08:00 committed by GitHub
parent f9dd7a1e64
commit d60a923841
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 409 additions and 471 deletions

1
.gitignore vendored
View File

@ -18,6 +18,7 @@ minecraft-exported-crash-info*
/build/ /build/
/HMCL/build/ /HMCL/build/
/HMCLCore/build/ /HMCLCore/build/
/HMCLBoot/build/
/HMCLTransformerDiscoveryService/build/ /HMCLTransformerDiscoveryService/build/
/minecraft/libraries/HMCLTransformerDiscoveryService/build/ /minecraft/libraries/HMCLTransformerDiscoveryService/build/
/buildSrc/build/ /buildSrc/build/

View File

@ -39,6 +39,7 @@ version = "$versionRoot.$buildNumber"
dependencies { dependencies {
implementation(project(":HMCLCore")) implementation(project(":HMCLCore"))
implementation(project(":HMCLBoot"))
implementation("libs:JFoenix") implementation("libs:JFoenix")
implementation(libs.twelvemonkeys.imageio.webp) implementation(libs.twelvemonkeys.imageio.webp)
implementation(libs.java.info) implementation(libs.java.info)
@ -89,18 +90,15 @@ fun attachSignature(jar: File) {
} }
} }
val java11 = sourceSets.create("java11") { tasks.withType<JavaCompile> {
java {
srcDir("src/main/java11")
}
}
tasks.getByName<JavaCompile>(java11.compileJavaTaskName) {
options.compilerArgs.add("--add-exports=java.base/jdk.internal.loader=ALL-UNNAMED")
sourceCompatibility = "11" sourceCompatibility = "11"
targetCompatibility = "11" targetCompatibility = "11"
} }
tasks.compileJava {
options.compilerArgs.add("--add-exports=java.base/jdk.internal.loader=ALL-UNNAMED")
}
tasks.jar { tasks.jar {
enabled = false enabled = false
dependsOn(tasks["shadowJar"]) dependsOn(tasks["shadowJar"])
@ -126,6 +124,7 @@ tasks.shadowJar {
exclude(dependency("com.google.code.gson:.*:.*")) exclude(dependency("com.google.code.gson:.*:.*"))
exclude(dependency("net.java.dev.jna:jna:.*")) exclude(dependency("net.java.dev.jna:jna:.*"))
exclude(dependency("libs:JFoenix:.*")) exclude(dependency("libs:JFoenix:.*"))
exclude(project(":HMCLBoot"))
} }
manifest { manifest {
@ -174,13 +173,6 @@ tasks.shadowJar {
} }
} }
tasks.processResources {
into("META-INF/versions/11") {
from(sourceSets["java11"].output)
}
dependsOn(tasks["java11Classes"])
}
val makeExecutables by tasks.registering { val makeExecutables by tasks.registering {
val extensions = listOf("exe", "sh") val extensions = listOf("exe", "sh")

View File

@ -17,41 +17,25 @@
*/ */
package org.jackhuang.hmcl; package org.jackhuang.hmcl;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import org.jackhuang.hmcl.util.FileSaver; import org.jackhuang.hmcl.util.FileSaver;
import org.jackhuang.hmcl.ui.AwtUtils; import org.jackhuang.hmcl.ui.AwtUtils;
import org.jackhuang.hmcl.util.ModuleHelper; import org.jackhuang.hmcl.util.ModuleHelper;
import org.jackhuang.hmcl.util.SelfDependencyPatcher; import org.jackhuang.hmcl.util.SelfDependencyPatcher;
import org.jackhuang.hmcl.ui.SwingUtils; import org.jackhuang.hmcl.util.SwingUtils;
import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.java.JavaRuntime;
import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.OperatingSystem;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Collections;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class Main { public final class EntryPoint {
private Main() { private EntryPoint() {
} }
public static void main(String[] args) { public static void main(String[] args) {
@ -64,10 +48,6 @@ public final class Main {
checkDirectoryPath(); checkDirectoryPath();
if (JavaRuntime.CURRENT_VERSION < 9)
// This environment check will take ~300ms
thread(Main::fixLetsEncrypt, "CA Certificate Check", true);
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
initIcon(); initIcon();
@ -97,6 +77,8 @@ public final class Main {
} }
} }
} catch (IOException e) { } catch (IOException e) {
// Logger has not been started yet, so print directly to System.err
System.err.println("Failed to create HMCL directory: " + Metadata.HMCL_CURRENT_DIRECTORY);
e.printStackTrace(System.err); e.printStackTrace(System.err);
showErrorAndExit(i18n("fatal.create_hmcl_current_directory_failure", Metadata.HMCL_CURRENT_DIRECTORY)); showErrorAndExit(i18n("fatal.create_hmcl_current_directory_failure", Metadata.HMCL_CURRENT_DIRECTORY));
} }
@ -112,16 +94,17 @@ public final class Main {
} }
private static void initIcon() { private static void initIcon() {
java.awt.Image image = java.awt.Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/assets/img/icon-mac.png")); java.awt.Image image = java.awt.Toolkit.getDefaultToolkit().getImage(EntryPoint.class.getResource("/assets/img/icon-mac.png"));
AwtUtils.setAppleIcon(image); AwtUtils.setAppleIcon(image);
} }
private static void checkDirectoryPath() { private static void checkDirectoryPath() {
String currentDirectory = new File("").getAbsolutePath(); String currentDir = System.getProperty("user.dir", "");
if (currentDirectory.contains("!")) { if (currentDir.contains("!")) {
LOG.error("The current working path contains an exclamation mark: " + currentDir);
// No Chinese translation because both Swing and JavaFX cannot render Chinese character properly when exclamation mark exists in the path. // No Chinese translation because both Swing and JavaFX cannot render Chinese character properly when exclamation mark exists in the path.
showErrorAndExit("Exclamation mark(!) is not allowed in the path where HMCL is in.\n" showErrorAndExit("Exclamation mark(!) is not allowed in the path where HMCL is in.\n"
+ "The path is " + currentDirectory); + "The path is " + currentDir);
} }
} }
@ -129,10 +112,10 @@ public final class Main {
try { try {
SelfDependencyPatcher.patch(); SelfDependencyPatcher.patch();
} catch (SelfDependencyPatcher.PatchException e) { } catch (SelfDependencyPatcher.PatchException e) {
LOG.error("unable to patch JVM", e); LOG.error("Unable to patch JVM", e);
showErrorAndExit(i18n("fatal.javafx.missing")); showErrorAndExit(i18n("fatal.javafx.missing"));
} catch (SelfDependencyPatcher.IncompatibleVersionException e) { } catch (SelfDependencyPatcher.IncompatibleVersionException e) {
LOG.error("unable to patch JVM", e); LOG.error("Unable to patch JVM", e);
showErrorAndExit(i18n("fatal.javafx.incompatible")); showErrorAndExit(i18n("fatal.javafx.incompatible"));
} catch (CancellationException e) { } catch (CancellationException e) {
LOG.error("User cancels downloading JavaFX", e); LOG.error("User cancels downloading JavaFX", e);
@ -149,7 +132,7 @@ public final class Main {
Class.forName("javafx.stage.Stage"); // javafx.graphics Class.forName("javafx.stage.Stage"); // javafx.graphics
Class.forName("javafx.scene.control.Skin"); // javafx.controls Class.forName("javafx.scene.control.Skin"); // javafx.controls
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(System.err); LOG.warning("JavaFX is incomplete or not found", e);
showErrorAndExit(i18n("fatal.javafx.incomplete")); showErrorAndExit(i18n("fatal.javafx.incomplete"));
} }
} }
@ -159,7 +142,7 @@ public final class Main {
try { try {
ModuleHelper.addEnableNativeAccess(Class.forName("javafx.stage.Stage")); // javafx.graphics ModuleHelper.addEnableNativeAccess(Class.forName("javafx.stage.Stage")); // javafx.graphics
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
e.printStackTrace(System.err); LOG.error("Failed to add enable native access for JavaFX", e);
showErrorAndExit(i18n("fatal.javafx.incomplete")); showErrorAndExit(i18n("fatal.javafx.incomplete"));
} }
} }
@ -174,7 +157,7 @@ public final class Main {
trySetMemoryAccessWarned.setAccessible(true); trySetMemoryAccessWarned.setAccessible(true);
trySetMemoryAccessWarned.invoke(null); trySetMemoryAccessWarned.invoke(null);
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(System.err); LOG.warning("Failed to enable unsafe memory access", e);
} }
} }
} }
@ -182,70 +165,8 @@ public final class Main {
/** /**
* Indicates that a fatal error has occurred, and that the application cannot start. * Indicates that a fatal error has occurred, and that the application cannot start.
*/ */
static void showErrorAndExit(String message) { private static void showErrorAndExit(String message) {
System.err.println(message);
System.err.println("A fatal error has occurred, forcibly exiting.");
try {
if (Platform.isFxApplicationThread()) {
new Alert(Alert.AlertType.ERROR, message).showAndWait();
exit(1);
}
} catch (Throwable ignored) {
}
SwingUtils.showErrorDialog(message); SwingUtils.showErrorDialog(message);
exit(1); exit(1);
} }
/**
* Indicates that potential issues have been detected, and that the application may not function properly (but it can still run).
*/
static void showWarningAndContinue(String message) {
System.err.println(message);
System.err.println("Potential issues have been detected.");
try {
if (Platform.isFxApplicationThread()) {
new Alert(Alert.AlertType.WARNING, message).showAndWait();
return;
}
} catch (Throwable ignored) {
}
SwingUtils.showWarningDialog(message);
}
private static void fixLetsEncrypt() {
try {
KeyStore defaultKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
Path ksPath = Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts");
try (InputStream ksStream = Files.newInputStream(ksPath)) {
defaultKeyStore.load(ksStream, "changeit".toCharArray());
}
KeyStore letsEncryptKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream letsEncryptFile = Main.class.getResourceAsStream("/assets/lekeystore.jks")) {
letsEncryptKeyStore.load(letsEncryptFile, "supersecretpassword".toCharArray());
}
KeyStore merged = KeyStore.getInstance(KeyStore.getDefaultType());
merged.load(null, new char[0]);
for (String alias : Collections.list(letsEncryptKeyStore.aliases()))
merged.setCertificateEntry(alias, letsEncryptKeyStore.getCertificate(alias));
for (String alias : Collections.list(defaultKeyStore.aliases()))
merged.setCertificateEntry(alias, defaultKeyStore.getCertificate(alias));
TrustManagerFactory instance = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
instance.init(merged);
SSLContext tls = SSLContext.getInstance("TLS");
tls.init(null, instance.getTrustManagers(), null);
HttpsURLConnection.setDefaultSSLSocketFactory(tls.getSocketFactory());
LOG.info("Added Lets Encrypt root certificates as additional trust");
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException |
KeyManagementException e) {
LOG.error("Failed to load lets encrypt certificate. Expect problems", e);
}
}
} }

View File

@ -81,13 +81,14 @@ public final class Launcher extends Application {
try { try {
try { try {
ConfigHolder.init(); ConfigHolder.init();
} catch (SambaException ignored) { } catch (SambaException e) {
Main.showWarningAndContinue(i18n("fatal.samba")); showAlert(AlertType.WARNING, i18n("fatal.samba"));
} catch (IOException e) { } catch (IOException e) {
LOG.error("Failed to load config", e); LOG.error("Failed to load config", e);
checkConfigInTempDir(); checkConfigInTempDir();
checkConfigOwner(); checkConfigOwner();
Main.showErrorAndExit(i18n("fatal.config_loading_failure", ConfigHolder.configLocation().getParent())); showAlert(AlertType.ERROR, i18n("fatal.config_loading_failure", ConfigHolder.configLocation().getParent()));
EntryPoint.exit(1);
} }
// https://lapcatsoftware.com/articles/app-translocation.html // https://lapcatsoftware.com/articles/app-translocation.html
@ -106,7 +107,7 @@ public final class Launcher extends Application {
} }
if (Metadata.HMCL_CURRENT_DIRECTORY.toString().indexOf('=') >= 0) { if (Metadata.HMCL_CURRENT_DIRECTORY.toString().indexOf('=') >= 0) {
Main.showWarningAndContinue(i18n("fatal.illegal_char")); showAlert(AlertType.WARNING, i18n("fatal.illegal_char"));
} }
// runLater to ensure ConfigHolder.init() finished initialization // runLater to ensure ConfigHolder.init() finished initialization
@ -169,7 +170,7 @@ public final class Launcher extends Application {
private static void checkConfigInTempDir() { private static void checkConfigInTempDir() {
if (ConfigHolder.isNewlyCreated() && isConfigInTempDir() if (ConfigHolder.isNewlyCreated() && isConfigInTempDir()
&& showAlert(AlertType.WARNING, i18n("fatal.config_in_temp_dir"), ButtonType.YES, ButtonType.NO) == ButtonType.NO) { && showAlert(AlertType.WARNING, i18n("fatal.config_in_temp_dir"), ButtonType.YES, ButtonType.NO) == ButtonType.NO) {
Main.exit(0); EntryPoint.exit(0);
} }
} }
@ -209,7 +210,7 @@ public final class Launcher extends Application {
Clipboard.getSystemClipboard() Clipboard.getSystemClipboard()
.setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, command)); .setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, command));
} }
Main.exit(1); EntryPoint.exit(1);
} }
@Override @Override

View File

@ -19,13 +19,10 @@ package org.jackhuang.hmcl;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.JarUtils; import org.jackhuang.hmcl.util.io.JarUtils;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.EnumSet;
/** /**
* Stores metadata about this application. * Stores metadata about this application.
@ -38,9 +35,6 @@ public final class Metadata {
public static final String FULL_NAME = "Hello Minecraft! Launcher"; public static final String FULL_NAME = "Hello Minecraft! Launcher";
public static final String VERSION = System.getProperty("hmcl.version.override", JarUtils.getManifestAttribute("Implementation-Version", "@develop@")); public static final String VERSION = System.getProperty("hmcl.version.override", JarUtils.getManifestAttribute("Implementation-Version", "@develop@"));
public static final int MINIMUM_REQUIRED_JAVA_VERSION = 8;
public static final int MINIMUM_SUPPORTED_JAVA_VERSION = 11;
public static final String TITLE = NAME + " " + VERSION; public static final String TITLE = NAME + " " + VERSION;
public static final String FULL_TITLE = FULL_NAME + " v" + VERSION; public static final String FULL_TITLE = FULL_NAME + " v" + VERSION;
@ -100,31 +94,4 @@ public final class Metadata {
return !isStable() && !isDev(); return !isStable() && !isDev();
} }
public static @Nullable String getSuggestedJavaDownloadLink() {
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX && Architecture.SYSTEM_ARCH == Architecture.LOONGARCH64_OW)
return "https://www.loongnix.cn/zh/api/java/downloads-jdk21/index.html";
else {
EnumSet<Architecture> supportedArchitectures;
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
supportedArchitectures = EnumSet.of(Architecture.X86_64, Architecture.X86, Architecture.ARM64);
else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX)
supportedArchitectures = EnumSet.of(
Architecture.X86_64, Architecture.X86,
Architecture.ARM64, Architecture.ARM32,
Architecture.RISCV64, Architecture.LOONGARCH64
);
else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
supportedArchitectures = EnumSet.of(Architecture.X86_64, Architecture.ARM64);
else
supportedArchitectures = EnumSet.noneOf(Architecture.class);
if (supportedArchitectures.contains(Architecture.SYSTEM_ARCH))
return String.format("https://docs.hmcl.net/downloads/%s/%s.html",
OperatingSystem.CURRENT_OS.getCheckedName(),
Architecture.SYSTEM_ARCH.getCheckedName()
);
else
return null;
}
}
} }

View File

@ -101,6 +101,7 @@ public final class ConfigHolder {
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
&& configLocation.getFileSystem() == FileSystems.getDefault() && configLocation.getFileSystem() == FileSystems.getDefault()
&& configLocation.toFile().canWrite()) { && configLocation.toFile().canWrite()) {
LOG.warning("Config at " + configLocation + " is not writable, but it seems to be a Samba share or OpenJDK bug");
// There are some serious problems with the implementation of Samba or OpenJDK // There are some serious problems with the implementation of Samba or OpenJDK
throw new SambaException(); throw new SambaException();
} else { } else {

View File

@ -42,7 +42,6 @@ import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.game.ModpackHelper; import org.jackhuang.hmcl.game.ModpackHelper;
import org.jackhuang.hmcl.java.JavaManager; import org.jackhuang.hmcl.java.JavaManager;
import org.jackhuang.hmcl.java.JavaRuntime;
import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.task.TaskExecutor;
@ -310,26 +309,6 @@ public final class Controllers {
} }
} }
if (JavaRuntime.CURRENT_VERSION < 10) {
Number shownTipVersion = null;
try {
shownTipVersion = (Number) config().getShownTips().get(JAVA_VERSION_TIP);
} catch (ClassCastException e) {
LOG.warning("Invalid type for shown tips key: " + JAVA_VERSION_TIP, e);
}
if (shownTipVersion == null || shownTipVersion.intValue() < Metadata.MINIMUM_SUPPORTED_JAVA_VERSION) {
MessageDialogPane.Builder builder = new MessageDialogPane.Builder(i18n("fatal.deprecated_java_version"), null, MessageType.WARNING);
String downloadLink = Metadata.getSuggestedJavaDownloadLink();
if (downloadLink != null)
builder.addHyperLink(i18n("fatal.deprecated_java_version.download_link", 21), downloadLink);
Controllers.dialog(builder
.ok(() -> config().getShownTips().put(JAVA_VERSION_TIP, Metadata.MINIMUM_SUPPORTED_JAVA_VERSION))
.build());
}
}
if (globalConfig().getAgreementVersion() < 1) { if (globalConfig().getAgreementVersion() < 1) {
JFXDialogLayout agreementPane = new JFXDialogLayout(); JFXDialogLayout agreementPane = new JFXDialogLayout();
agreementPane.setHeading(new Label(i18n("launcher.agreement"))); agreementPane.setHeading(new Label(i18n("launcher.agreement")));

View File

@ -21,16 +21,16 @@ import com.google.gson.Gson;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import javafx.application.Platform; import javafx.application.Platform;
import org.jackhuang.hmcl.EntryPoint;
import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.UpgradeDialog; import org.jackhuang.hmcl.ui.UpgradeDialog;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.ui.SwingUtils; import org.jackhuang.hmcl.util.SwingUtils;
import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.TaskCancellationAction;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.JarUtils; import org.jackhuang.hmcl.util.io.JarUtils;
@ -103,16 +103,6 @@ public final class UpdateHandler {
return; return;
} }
if (JavaRuntime.CURRENT_VERSION < Metadata.MINIMUM_SUPPORTED_JAVA_VERSION) {
MessageDialogPane.Builder builder = new MessageDialogPane.Builder(i18n("fatal.deprecated_java_version.update"), i18n("message.error"), MessageType.ERROR);
String downloadLink = Metadata.getSuggestedJavaDownloadLink();
if (downloadLink != null)
builder.addHyperLink(i18n("fatal.deprecated_java_version.download_link", 21), downloadLink);
builder.ok(null);
Controllers.dialog(builder.build());
return;
}
Controllers.dialog(new UpgradeDialog(version, () -> { Controllers.dialog(new UpgradeDialog(version, () -> {
Path downloaded; Path downloaded;
try { try {
@ -136,7 +126,7 @@ public final class UpdateHandler {
} }
requestUpdate(downloaded, getCurrentLocation()); requestUpdate(downloaded, getCurrentLocation());
Main.exit(0); EntryPoint.exit(0);
} catch (IOException e) { } catch (IOException e) {
LOG.warning("Failed to update to " + version, e); LOG.warning("Failed to update to " + version, e);
Platform.runLater(() -> Controllers.dialog(StringUtils.getStackTrace(e), i18n("update.failed"), MessageType.ERROR)); Platform.runLater(() -> Controllers.dialog(StringUtils.getStackTrace(e), i18n("update.failed"), MessageType.ERROR));

View File

@ -17,11 +17,16 @@
*/ */
package org.jackhuang.hmcl.util; package org.jackhuang.hmcl.util;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
import java.util.Set; import java.util.Set;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/** /**
* Utility for Adding JavaFX to module path. * Utility for Adding JavaFX to module path.
* *
@ -31,7 +36,43 @@ public final class JavaFXPatcher {
private JavaFXPatcher() { private JavaFXPatcher() {
} }
public static void patch(Set<String> modules, Path[] jarPaths, String[] addOpens) { /**
LOG.info("No need to patch JavaFX with Java 8"); * Add JavaFX to module path at runtime.
*
* @param modules All module names
* @param jarPaths All jar paths
* @throws ReflectiveOperationException When the call to add these jars to the system module path failed.
*/
public static void patch(Set<String> modules, Path[] jarPaths, String[] addOpens) throws ReflectiveOperationException {
// Find all modules
ModuleFinder finder = ModuleFinder.of(jarPaths);
// Load all modules as unnamed module
for (ModuleReference mref : finder.findAll()) {
((jdk.internal.loader.BuiltinClassLoader) ClassLoader.getSystemClassLoader()).loadModule(mref);
}
// Define all modules
Configuration config = Configuration.resolveAndBind(finder, List.of(ModuleLayer.boot().configuration()), finder, modules);
ModuleLayer layer = ModuleLayer.defineModules(config, List.of(ModuleLayer.boot()), name -> ClassLoader.getSystemClassLoader()).layer();
// Add-Exports and Add-Opens
try {
// Some hacks
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Module.class, MethodHandles.lookup());
MethodHandle handle = lookup.findVirtual(Module.class, "implAddOpensToAllUnnamed", MethodType.methodType(void.class, String.class));
for (String target : addOpens) {
String[] name = target.split("/", 2); // <module>/<package>
layer.findModule(name[0]).ifPresent(m -> {
try {
handle.invokeWithArguments(m, name[1]);
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
});
}
} catch (Throwable t) {
throw new ReflectiveOperationException(t);
}
} }
} }

View File

@ -17,13 +17,28 @@
*/ */
package org.jackhuang.hmcl.util; package org.jackhuang.hmcl.util;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
/** /**
* @author Glavo * @author Glavo
*/ */
public final class ModuleHelper { public final class ModuleHelper {
public static void addEnableNativeAccess(Class<?> clazzInModule) { public static void addEnableNativeAccess(Class<?> clazzInModule) {
// do nothing Module module = clazzInModule.getModule();
if (module.isNamed()) {
try {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Module.class, MethodHandles.lookup());
MethodHandle implAddEnableNativeAccess = lookup.findVirtual(Module.class, "implAddEnableNativeAccess", MethodType.methodType(Module.class));
Module ignored = (Module) implAddEnableNativeAccess.invokeExact(module);
} catch (Throwable e) {
e.printStackTrace(System.err);
}
} else {
System.err.println("TODO: Add enable native access for anonymous modules is not yet supported");
}
} }
private ModuleHelper() { private ModuleHelper() {

View File

@ -41,9 +41,8 @@
*/ */
package org.jackhuang.hmcl.util; package org.jackhuang.hmcl.util;
import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.EntryPoint;
import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.ui.SwingUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.ChecksumMismatchException; import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.io.IOUtils;
@ -181,12 +180,6 @@ public final class SelfDependencyPatcher {
// We are probably on 8 and its on 11 // We are probably on 8 and its on 11
throw new IncompatibleVersionException(); throw new IncompatibleVersionException();
} }
// So the problem with Java 8 is that some distributions DO NOT BUNDLE JAVAFX
// Why is this a problem? OpenJFX does not come in public bundles prior to Java 11
// So you're out of luck unless you change your JDK or update Java.
if (JavaRuntime.CURRENT_VERSION < 11) {
throw new IncompatibleVersionException();
}
SelfDependencyPatcher patcher = new SelfDependencyPatcher(); SelfDependencyPatcher patcher = new SelfDependencyPatcher();
@ -246,7 +239,7 @@ public final class SelfDependencyPatcher {
} }
} else { } else {
LOG.info("User choose not to download JavaFX"); LOG.info("User choose not to download JavaFX");
Main.exit(0); EntryPoint.exit(0);
} }
throw new AssertionError(); throw new AssertionError();
} }

View File

@ -20,7 +20,6 @@ package org.jackhuang.hmcl.util.i18n;
import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.JsonAdapter;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import org.jackhuang.hmcl.java.JavaInfo;
import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Lang;
import java.io.IOException; import java.io.IOException;
@ -116,10 +115,7 @@ public final class Locales {
if (resourceBundle == null) { if (resourceBundle == null) {
if (this != DEFAULT && this.locale == DEFAULT.locale) { if (this != DEFAULT && this.locale == DEFAULT.locale) {
bundle = DEFAULT.getResourceBundle(); bundle = DEFAULT.getResourceBundle();
} else if (JavaInfo.CURRENT_ENVIRONMENT.getParsedVersion() < 9) {
bundle = ResourceBundle.getBundle("assets.lang.I18N", locale, UTF8Control.INSTANCE);
} else { } else {
// Java 9+ uses UTF-8 as the default encoding for resource bundles
bundle = ResourceBundle.getBundle("assets.lang.I18N", locale); bundle = ResourceBundle.getBundle("assets.lang.I18N", locale);
} }

View File

@ -1,78 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.nio.file.Path;
import java.util.List;
import java.util.Set;
/**
* Utility for Adding JavaFX to module path.
*
* @author ZekerZhayard
*/
public final class JavaFXPatcher {
private JavaFXPatcher() {
}
/**
* Add JavaFX to module path at runtime.
*
* @param modules All module names
* @param jarPaths All jar paths
* @throws ReflectiveOperationException When the call to add these jars to the system module path failed.
*/
public static void patch(Set<String> modules, Path[] jarPaths, String[] addOpens) throws ReflectiveOperationException {
// Find all modules
ModuleFinder finder = ModuleFinder.of(jarPaths);
// Load all modules as unnamed module
for (ModuleReference mref : finder.findAll()) {
((jdk.internal.loader.BuiltinClassLoader) ClassLoader.getSystemClassLoader()).loadModule(mref);
}
// Define all modules
Configuration config = Configuration.resolveAndBind(finder, List.of(ModuleLayer.boot().configuration()), finder, modules);
ModuleLayer layer = ModuleLayer.defineModules(config, List.of(ModuleLayer.boot()), name -> ClassLoader.getSystemClassLoader()).layer();
// Add-Exports and Add-Opens
try {
// Some hacks
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Module.class, MethodHandles.lookup());
MethodHandle handle = lookup.findVirtual(Module.class, "implAddOpensToAllUnnamed", MethodType.methodType(void.class, String.class));
for (String target : addOpens) {
String[] name = target.split("/", 2); // <module>/<package>
layer.findModule(name[0]).ifPresent(m -> {
try {
handle.invokeWithArguments(m, name[1]);
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
});
}
} catch (Throwable t) {
throw new ReflectiveOperationException(t);
}
}
}

View File

@ -1,46 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
/**
* @author Glavo
*/
public final class ModuleHelper {
public static void addEnableNativeAccess(Class<?> clazzInModule) {
Module module = clazzInModule.getModule();
if (module.isNamed()) {
try {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Module.class, MethodHandles.lookup());
MethodHandle implAddEnableNativeAccess = lookup.findVirtual(Module.class, "implAddEnableNativeAccess", MethodType.methodType(Module.class));
Module ignored = (Module) implAddEnableNativeAccess.invokeExact(module);
} catch (Throwable e) {
e.printStackTrace(System.err);
}
} else {
System.err.println("TODO: Add enable native access for anonymous modules is not yet supported");
}
}
private ModuleHelper() {
}
}

View File

@ -1,23 +0,0 @@
package org.jackhuang.hmcl.util.logging;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* @author Glavo
*/
final class CallerFinder {
private static final String PACKAGE_PREFIX = CallerFinder.class.getPackageName() + ".";
private static final Predicate<StackWalker.StackFrame> PREDICATE = stackFrame -> !stackFrame.getClassName().startsWith(PACKAGE_PREFIX);
private static final Function<Stream<StackWalker.StackFrame>, Optional<StackWalker.StackFrame>> FUNCTION = stream -> stream.filter(PREDICATE).findFirst();
private static final Function<StackWalker.StackFrame, String> FRAME_MAPPING = frame -> frame.getClassName() + "." + frame.getMethodName();
static String getCaller() {
return StackWalker.getInstance().walk(FUNCTION).map(FRAME_MAPPING).orElse(null);
}
private CallerFinder() {
}
}

View File

@ -398,16 +398,6 @@ fatal.apply_update_failure=We are sorry, but Hello Minecraft! Launcher is unable
fatal.apply_update_need_win7=Hello Minecraft! Launcher cannot automatically update on Windows XP/Vista.\n\ fatal.apply_update_need_win7=Hello Minecraft! Launcher cannot automatically update on Windows XP/Vista.\n\
\n\ \n\
You can update manually by downloading a newer launcher version from %s. You can update manually by downloading a newer launcher version from %s.
fatal.deprecated_java_version=HMCL will require Java 11 or later to run in the future, but will still support launching games with Java 8.\n\
\n\
It is recommended to install the latest version of Java to ensure that HMCL works properly.\n\
\n\
You can continue to keep the old version of Java. HMCL can recognize and manage multiple Java versions and will automatically select the appropriate Java for you based on the game version.
fatal.deprecated_java_version.update=HMCL will require Java 11 or later to run in the future. Please install the latest version of Java to ensure that HMCL can complete the upgrade.\n\
\n\
You can continue to keep the old version of Java.\
HMCL can recognize and manage multiple Java versions and will automatically select the appropriate Java for you based on the game version.
fatal.deprecated_java_version.download_link=Download Java %d
fatal.samba=If you opened Hello Minecraft! Launcher from a Samba network drive, some features might not be working. Please try updating your Java or moving the launcher to another directory. fatal.samba=If you opened Hello Minecraft! Launcher from a Samba network drive, some features might not be working. Please try updating your Java or moving the launcher to another directory.
fatal.illegal_char=Your user path contains an illegal character "=". You will not be able to use authlib-injector or change the skin of your offline account. fatal.illegal_char=Your user path contains an illegal character "=". You will not be able to use authlib-injector or change the skin of your offline account.
fatal.unsupported_platform=Minecraft is not fully supported on your platform yet, so you may experience missing features or even be unable to launch the game.\n\ fatal.unsupported_platform=Minecraft is not fully supported on your platform yet, so you may experience missing features or even be unable to launch the game.\n\

View File

@ -400,17 +400,6 @@ fatal.apply_update_failure=Lo sentimos, pero Hello Minecraft! Launcher no puede
fatal.apply_update_need_win7=Hello Minecraft! Launcher no puede actualizarse automáticamente en Windows XP/Vista.\n\ fatal.apply_update_need_win7=Hello Minecraft! Launcher no puede actualizarse automáticamente en Windows XP/Vista.\n\
\n\ \n\
Puedes actualizar manualmente descargando una versión más reciente del launcher desde %s. Puedes actualizar manualmente descargando una versión más reciente del launcher desde %s.
fatal.deprecated_java_version=HMCL requerirá Java 11 o posterior para funcionar en el futuro, pero seguirá siendo compatible con el lanzamiento de juegos con Java 8.\n\
\n\
Se recomienda instalar la última versión de Java para garantizar el correcto funcionamiento de HMCL.\n\
\n\
Puedes conservar la versión antigua de Java. HMCL puede reconocer y gestionar varias versiones de Java y elegirá automáticamente la adecuada para usted en función de la versión del juego.
fatal.deprecated_java_version.update=HMCL requerirá Java 11 o posterior para funcionar en el futuro. Por favor, instalar la última versión de Java para asegurarse de que el lanzador puede completar la actualización.\n\
\n\
Puedes conservar la versión antigua de Java.\
HMCL puede reconocer y gestionar varias versiones de Java \
y elegirá automáticamente la adecuada para usted en función de la versión del juego.
fatal.deprecated_java_version.download_link=Descargar Java %d
fatal.samba=Si ha abierto Hello Minecraft! Launcher desde una unidad de red Samba, es posible que algunas funciones no funcionen. Intenta actualizar tu Java o mover el launcher a otro directorio. fatal.samba=Si ha abierto Hello Minecraft! Launcher desde una unidad de red Samba, es posible que algunas funciones no funcionen. Intenta actualizar tu Java o mover el launcher a otro directorio.
fatal.illegal_char=Su ruta de usuario contiene un carácter ilegal «=», por lo que algunas características podrían no funcionar correctamente.\n\ fatal.illegal_char=Su ruta de usuario contiene un carácter ilegal «=», por lo que algunas características podrían no funcionar correctamente.\n\
Por ejemplo, no podrá utilizar authlib-injector o cambiar el skin de su cuenta offline. Por ejemplo, no podrá utilizar authlib-injector o cambiar el skin de su cuenta offline.
@ -446,7 +435,6 @@ folder.shaderpacks=Paquetes de sombreados
folder.saves=Mundos folder.saves=Mundos
folder.schematics=Esquemas folder.schematics=Esquemas
folder.screenshots=Capturas de pantalla folder.screenshots=Capturas de pantalla
folder.world=Directorio del mundo
game=Juegos game=Juegos
game.crash.feedback=<b>¡Por favor, no comparta capturas de pantalla de esta interfaz con otras personas!</b> Si pide ayuda a otras personas, haga clic en <b>Exportar registros de colgado</b> y envíe el archivo exportado a otras personas para que lo analicen. game.crash.feedback=<b>¡Por favor, no comparta capturas de pantalla de esta interfaz con otras personas!</b> Si pide ayuda a otras personas, haga clic en <b>Exportar registros de colgado</b> y envíe el archivo exportado a otras personas para que lo analicen.
@ -944,16 +932,11 @@ modpack.wizard.step.initialization.warning=Antes de hacer un modpack, por favor
modpack.wizard.step.initialization.server=Haga clic aquí para ver más tutoriales para hacer un modpack de servidor que se pueda actualizar automáticamente. modpack.wizard.step.initialization.server=Haga clic aquí para ver más tutoriales para hacer un modpack de servidor que se pueda actualizar automáticamente.
modrinth.category.adventure=Aventura modrinth.category.adventure=Aventura
modrinth.category.atmosphere=Atmósfera
modrinth.category.audio=Audio modrinth.category.audio=Audio
modrinth.category.blocks=Bloqueos modrinth.category.blocks=Bloqueos
modrinth.category.bloom=Resplandor
modrinth.category.bukkit=Bukkit modrinth.category.bukkit=Bukkit
modrinth.category.bungeecord=BungeeCord modrinth.category.bungeecord=BungeeCord
modrinth.category.canvas=Canvas
modrinth.category.cartoon=Caricatura
modrinth.category.challenging=Desafío modrinth.category.challenging=Desafío
modrinth.category.colored-lighting=Iluminación de color
modrinth.category.core-shaders=Sombreadores de núcleo modrinth.category.core-shaders=Sombreadores de núcleo
modrinth.category.combat=Combate modrinth.category.combat=Combate
modrinth.category.cursed=Maldición modrinth.category.cursed=Maldición
@ -963,25 +946,19 @@ modrinth.category.entities=Entidades
modrinth.category.environment=Medio ambiente modrinth.category.environment=Medio ambiente
modrinth.category.equipment=Equipo modrinth.category.equipment=Equipo
modrinth.category.fabric=Fabric modrinth.category.fabric=Fabric
modrinth.category.fantasy=Fantasía
modrinth.category.foliage=Vegetación
modrinth.category.fonts=Fonts modrinth.category.fonts=Fonts
modrinth.category.food=Alimentos modrinth.category.food=Alimentos
modrinth.category.forge=Forge modrinth.category.forge=Forge
modrinth.category.game-mechanics=Mecánica de juego modrinth.category.game-mechanics=Mecánica de juego
modrinth.category.gui=GUI modrinth.category.gui=GUI
modrinth.category.high=Alto
modrinth.category.iris=Iris
modrinth.category.items=Objetos modrinth.category.items=Objetos
modrinth.category.kitchen-sink=Fregadero de cocina modrinth.category.kitchen-sink=Fregadero de cocina
modrinth.category.library=Biblioteca modrinth.category.library=Biblioteca
modrinth.category.lightweight=Peso ligero modrinth.category.lightweight=Peso ligero
modrinth.category.liteloader=LiteLoader modrinth.category.liteloader=LiteLoader
modrinth.category.locale=Locale modrinth.category.locale=Locale
modrinth.category.low=Bajo
modrinth.category.magic=Magia modrinth.category.magic=Magia
modrinth.category.management=Gestión modrinth.category.management=Gestión
modrinth.category.medium=Medio
modrinth.category.minecraft=Minecraft modrinth.category.minecraft=Minecraft
modrinth.category.minigame=Minijuego modrinth.category.minigame=Minijuego
modrinth.category.misc=Misceláneo modrinth.category.misc=Misceláneo
@ -991,21 +968,13 @@ modrinth.category.models=Modelos
modrinth.category.modloader=Cargador de mods modrinth.category.modloader=Cargador de mods
modrinth.category.multiplayer=Multijugador modrinth.category.multiplayer=Multijugador
modrinth.category.neoforge=NeoForge modrinth.category.neoforge=NeoForge
modrinth.category.optifine=OptiFine
modrinth.category.optimization=Optimización modrinth.category.optimization=Optimización
modrinth.category.paper=Paper modrinth.category.paper=Paper
modrinth.category.path-tracing=Trazado de rutas
modrinth.category.pbr=PBR
modrinth.category.potato=Mínimo
modrinth.category.purpur=Purpur modrinth.category.purpur=Purpur
modrinth.category.quests=Misiones modrinth.category.quests=Misiones
modrinth.category.quilt=Quilt modrinth.category.quilt=Quilt
modrinth.category.realistic=Realista modrinth.category.realistic=Realista
modrinth.category.reflections=Reflexiones
modrinth.category.rift=Rift modrinth.category.rift=Rift
modrinth.category.screenshot=Extremo
modrinth.category.semi-realistic=Semirrealista
modrinth.category.shadows=Sombras
modrinth.category.simplistic=Simplista modrinth.category.simplistic=Simplista
modrinth.category.social=Social modrinth.category.social=Social
modrinth.category.spigot=Spigot modrinth.category.spigot=Spigot
@ -1016,7 +985,6 @@ modrinth.category.themed=Temática
modrinth.category.transportation=Transporte modrinth.category.transportation=Transporte
modrinth.category.tweaks=Ajustes modrinth.category.tweaks=Ajustes
modrinth.category.utility=Utilidad modrinth.category.utility=Utilidad
modrinth.category.vanilla=Vainilla
modrinth.category.vanilla-like=Similar a la vainilla modrinth.category.vanilla-like=Similar a la vainilla
modrinth.category.velocity=Velocity modrinth.category.velocity=Velocity
modrinth.category.waterfall=Waterfall modrinth.category.waterfall=Waterfall
@ -1104,12 +1072,7 @@ world.backup.create.locked=El mundo está actualmente en uso. Por favor, cierra
world.backup.create.success=Creada con éxito una nueva copia de seguridad: %s world.backup.create.success=Creada con éxito una nueva copia de seguridad: %s
world.backup.delete=Eliminar esta copia de seguridad world.backup.delete=Eliminar esta copia de seguridad
world.backup.processing=Creando nueva copia de seguridad ... world.backup.processing=Creando nueva copia de seguridad ...
world.chunkbase=Chunk Base world.datapack=Gestionar paquetes de datos
world.chunkbase.end_city=Ciudad del End
world.chunkbase.seed_map=Vista previa de la generación mundial
world.chunkbase.stronghold=Fortaleza
world.chunkbase.nether_fortress=Fortaleza del Nether
world.datapack=Paquetes de datos
world.datetime=Jugado por última vez en %s world.datetime=Jugado por última vez en %s
world.download=Descargar Mundo world.download=Descargar Mundo
world.download.title=Descargar mundo - %1s world.download.title=Descargar mundo - %1s
@ -1154,8 +1117,7 @@ world.info.time=Tiempo de juego
world.info.time.format=%s días world.info.time.format=%s días
world.locked=En uso world.locked=En uso
world.manage=Mundos world.manage=Mundos
world.manage.button=Administrar world.manage.title=Mundos - %s
world.manage.title=Mundo - %s
world.name=Nombre del mundo world.name=Nombre del mundo
world.name.enter=Introducir el nombre del mundo world.name.enter=Introducir el nombre del mundo
world.show_all=Mostrar todo world.show_all=Mostrar todo

View File

@ -398,17 +398,6 @@ fatal.apply_update_failure=Мы сожалеем, лаунчер не смог
Вы можете обновить программу вручную, скачав более новую версию с %s. Вы можете обновить программу вручную, скачав более новую версию с %s.
fatal.apply_update_need_win7=Лаунчер не может автоматически обновляться на Windows XP/Vista.\n\ fatal.apply_update_need_win7=Лаунчер не может автоматически обновляться на Windows XP/Vista.\n\
Вы можете обновить программу вручную, скачав более новую версию с %s. Вы можете обновить программу вручную, скачав более новую версию с %s.
fatal.deprecated_java_version=В будущем для работы HMCL потребуется Java 11 или более поздняя версия, однако запуск игр будет поддерживаться и на Java 8.\n\
\n\
Рекомендуется установить последнюю версию Java, чтобы обеспечить правильную работу HMCL.\n\
\n\
Вы можете продолжать использовать старую версию Java. HMCL может распознавать и управлять несколькими версиями Java и автоматически выбирать подходящую Java в зависимости от версии игры.
fatal.deprecated_java_version.update=В будущем для работы HMCL потребуется Java 11 или более поздняя версия. Пожалуйста, установить последнюю версию Java, чтобы убедиться, что приложение сможет завершить обновление.\n\
\n\
Вы можете продолжать использовать старую версию Java.\
HMCL может распознавать и управлять несколькими версиями Java \
и автоматически выбирать подходящую Java в зависимости от версии игры.
fatal.deprecated_java_version.download_link=Скачать Java %d
fatal.samba=Если вы пытаетесь открыть лаунчер в общей папке Samba, он может не работать, попробуйте обновить Java или запустить лаунчер из локальной папки. fatal.samba=Если вы пытаетесь открыть лаунчер в общей папке Samba, он может не работать, попробуйте обновить Java или запустить лаунчер из локальной папки.
fatal.illegal_char=Недопустимый символ «=» в пути к папке пользователя. Лаунчер может работать, но некоторые функции не будут работать.\n\ fatal.illegal_char=Недопустимый символ «=» в пути к папке пользователя. Лаунчер может работать, но некоторые функции не будут работать.\n\
Вы не сможете использовать аккаунт authlib-injector или изменить скин для аккаунта в режиме офлайн. Вы не сможете использовать аккаунт authlib-injector или изменить скин для аккаунта в режиме офлайн.
@ -444,7 +433,6 @@ folder.shaderpacks=Пакеты шейдеров
folder.saves=Миры folder.saves=Миры
folder.schematics=Схемы folder.schematics=Схемы
folder.screenshots=Снимки экрана folder.screenshots=Снимки экрана
folder.world=Папка мира
game=Игры game=Игры
game.crash.feedback=<b>Пожалуйста, не делитесь скриншотами этого интерфейса с другими!</b> Если вы просите помощи у других, нажмите <b>«Экспорт журналов с ошибками»</b> и отправьте экспортированный файл другим для анализа. game.crash.feedback=<b>Пожалуйста, не делитесь скриншотами этого интерфейса с другими!</b> Если вы просите помощи у других, нажмите <b>«Экспорт журналов с ошибками»</b> и отправьте экспортированный файл другим для анализа.
@ -943,16 +931,11 @@ modpack.wizard.step.initialization.warning=Перед созданием мод
modpack.wizard.step.initialization.server=Нажмите здесь для получения дополнительных руководств по созданию модпака на сервере, который может автообновляться. modpack.wizard.step.initialization.server=Нажмите здесь для получения дополнительных руководств по созданию модпака на сервере, который может автообновляться.
modrinth.category.adventure=Приключение modrinth.category.adventure=Приключение
modrinth.category.atmosphere=Атмосфера
modrinth.category.audio=Аудио modrinth.category.audio=Аудио
modrinth.category.blocks=Блоки modrinth.category.blocks=Блоки
modrinth.category.bloom=Свечение
modrinth.category.bukkit=Bukkit modrinth.category.bukkit=Bukkit
modrinth.category.bungeecord=BungeeCord modrinth.category.bungeecord=BungeeCord
modrinth.category.canvas=Canvas
modrinth.category.cartoon=Мультяшный
modrinth.category.challenging=Вызов modrinth.category.challenging=Вызов
modrinth.category.colored-lighting=Цветное освещение
modrinth.category.core-shaders=Ядро шейдеров modrinth.category.core-shaders=Ядро шейдеров
modrinth.category.combat=Бой modrinth.category.combat=Бой
modrinth.category.cursed=Cursed modrinth.category.cursed=Cursed
@ -962,25 +945,19 @@ modrinth.category.entities=Сущности
modrinth.category.environment=Окружающая среда modrinth.category.environment=Окружающая среда
modrinth.category.equipment=Оборудование modrinth.category.equipment=Оборудование
modrinth.category.fabric=Fabric modrinth.category.fabric=Fabric
modrinth.category.fantasy=Фэнтези
modrinth.category.foliage=Растительность
modrinth.category.fonts=Шрифты modrinth.category.fonts=Шрифты
modrinth.category.food=Еда modrinth.category.food=Еда
modrinth.category.forge=Forge modrinth.category.forge=Forge
modrinth.category.game-mechanics=Игровая механика modrinth.category.game-mechanics=Игровая механика
modrinth.category.gui=ГИП modrinth.category.gui=ГИП
modrinth.category.high=Высокий
modrinth.category.iris=Iris
modrinth.category.items=Предметы modrinth.category.items=Предметы
modrinth.category.kitchen-sink=Kitchen-Sink modrinth.category.kitchen-sink=Kitchen-Sink
modrinth.category.library=Библиотека modrinth.category.library=Библиотека
modrinth.category.lightweight=Легкий modrinth.category.lightweight=Легкий
modrinth.category.liteloader=LiteLoader modrinth.category.liteloader=LiteLoader
modrinth.category.locale=Язык modrinth.category.locale=Язык
modrinth.category.low=Низкий
modrinth.category.magic=Магия modrinth.category.magic=Магия
modrinth.category.management=Управление modrinth.category.management=Управление
modrinth.category.medium=Средний
modrinth.category.minecraft=Minecraft modrinth.category.minecraft=Minecraft
modrinth.category.minigame=Мини-игра modrinth.category.minigame=Мини-игра
modrinth.category.misc=Разное modrinth.category.misc=Разное
@ -990,21 +967,13 @@ modrinth.category.models=Модели
modrinth.category.modloader=Мод-загрузчик modrinth.category.modloader=Мод-загрузчик
modrinth.category.multiplayer=Сетевая игра modrinth.category.multiplayer=Сетевая игра
modrinth.category.neoforge=NeoForge modrinth.category.neoforge=NeoForge
modrinth.category.optifine=OptiFine
modrinth.category.optimization=Оптимизация modrinth.category.optimization=Оптимизация
modrinth.category.paper=Paper modrinth.category.paper=Paper
modrinth.category.path-tracing=Трассировка пути
modrinth.category.pbr=PBR
modrinth.category.potato=Минимум
modrinth.category.purpur=Purpur modrinth.category.purpur=Purpur
modrinth.category.quests=Квесты modrinth.category.quests=Квесты
modrinth.category.quilt=Quilt modrinth.category.quilt=Quilt
modrinth.category.realistic=Реалистичный modrinth.category.realistic=Реалистичный
modrinth.category.reflections=Отражения
modrinth.category.rift=Rift modrinth.category.rift=Rift
modrinth.category.screenshot=Экстрим
modrinth.category.semi-realistic=Полуреалистичный
modrinth.category.shadows=Тени
modrinth.category.simplistic=Упрощенный modrinth.category.simplistic=Упрощенный
modrinth.category.social=Социальная modrinth.category.social=Социальная
modrinth.category.spigot=Spigot modrinth.category.spigot=Spigot
@ -1015,7 +984,6 @@ modrinth.category.themed=Тематический
modrinth.category.transportation=Транспорт modrinth.category.transportation=Транспорт
modrinth.category.tweaks=Усовершенствование modrinth.category.tweaks=Усовершенствование
modrinth.category.utility=Утилиты modrinth.category.utility=Утилиты
modrinth.category.vanilla=Ванильное
modrinth.category.vanilla-like=Ванильное modrinth.category.vanilla-like=Ванильное
modrinth.category.velocity=Velocity modrinth.category.velocity=Velocity
modrinth.category.waterfall=Waterfall modrinth.category.waterfall=Waterfall
@ -1103,12 +1071,7 @@ world.backup.create.locked=В настоящее время мир находи
world.backup.create.success=Успешно создано новое резервное копирование: %s world.backup.create.success=Успешно создано новое резервное копирование: %s
world.backup.delete=Удалить эту резервную копию world.backup.delete=Удалить эту резервную копию
world.backup.processing=Создание новой резервной копии ... world.backup.processing=Создание новой резервной копии ...
world.chunkbase=Chunk Base world.datapack=Управлять наборами данных
world.chunkbase.end_city=Город Края
world.chunkbase.seed_map=Предпросмотр генерации мира
world.chunkbase.stronghold=Крепость
world.chunkbase.nether_fortress=Крепость Нижнего мира
world.datapack=Наборы данных
world.datetime=Последний запуск игры %s world.datetime=Последний запуск игры %s
world.download=Скачать мир world.download=Скачать мир
world.download.title=Скачать мир - %1s world.download.title=Скачать мир - %1s
@ -1153,8 +1116,7 @@ world.info.time=Время игры
world.info.time.format=%s дн. world.info.time.format=%s дн.
world.locked=В эксплуатации world.locked=В эксплуатации
world.manage=Миры world.manage=Миры
world.manage.button=Управлять world.manage.title=Миры - %s
world.manage.title=Мир - %s
world.name=Название мира world.name=Название мира
world.name.enter=Введите название мира world.name.enter=Введите название мира
world.show_all=Показать все world.show_all=Показать все

View File

@ -380,9 +380,6 @@ fatal.mac_app_translocation=由於 macOS 的安全機制Hello Minecraft! Laun
fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即將升級完成,請重新開啟 HMCL。 fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即將升級完成,請重新開啟 HMCL。
fatal.apply_update_failure=我們很抱歉 Hello Minecraft! Launcher 無法自動完成升級程式,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 HMCL 來完成升級。 fatal.apply_update_failure=我們很抱歉 Hello Minecraft! Launcher 無法自動完成升級程式,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 HMCL 來完成升級。
fatal.apply_update_need_win7=Hello Minecraft! Launcher 無法在 Windows XP/Vista 上進行自動更新。請從 %s 處手動下載 HMCL 來完成升級。 fatal.apply_update_need_win7=Hello Minecraft! Launcher 無法在 Windows XP/Vista 上進行自動更新。請從 %s 處手動下載 HMCL 來完成升級。
fatal.deprecated_java_version=HMCL 未來需要 Java 11 或更高版本才能執行,但依然支援使用 Java 8 啟動遊戲。建議安裝最新版本的 Java 以確保 HMCL 正常執行。\n你可以繼續保留舊版本 Java。HMCL 能夠識別與管理多個 Java並會自動根據遊戲版本為你選取合適的 Java。
fatal.deprecated_java_version.update=更高版本的 HMCL 需要 Java 11 或更高版本才能執行,請安裝最新版本的 Java 以確保 HMCL 能夠完成升級。\n你可以繼續保留舊版本 Java。HMCL 能夠識別與管理多個 Java並會自動根據遊戲版本為你選取合適的 Java。
fatal.deprecated_java_version.download_link=下載 Java %d
fatal.samba=如果您正在透過 Samba 共亯的目錄中開啟 Hello Minecraft! Launcher啟動器可能無法正常工作請嘗試更新您的 Java 或在本機目錄內開啟 HMCL。 fatal.samba=如果您正在透過 Samba 共亯的目錄中開啟 Hello Minecraft! Launcher啟動器可能無法正常工作請嘗試更新您的 Java 或在本機目錄內開啟 HMCL。
fatal.illegal_char=由於您的使用者目錄路徑中存在無效字元『=』,您將無法使用外部登入帳戶以及離線登入更換外觀功能。 fatal.illegal_char=由於您的使用者目錄路徑中存在無效字元『=』,您將無法使用外部登入帳戶以及離線登入更換外觀功能。
fatal.unsupported_platform=Minecraft 尚未你您的平臺提供完善支援,所以可能影響遊戲體驗或無法啟動遊戲。\n若無法啟動 Minecraft 1.17 及更高版本,可以嘗試在「(全域/實例特定) 遊戲設定 → 進階設定 → 除錯選項」中將「繪製器」切換為「軟繪製器」,以獲得更好的相容性。 fatal.unsupported_platform=Minecraft 尚未你您的平臺提供完善支援,所以可能影響遊戲體驗或無法啟動遊戲。\n若無法啟動 Minecraft 1.17 及更高版本,可以嘗試在「(全域/實例特定) 遊戲設定 → 進階設定 → 除錯選項」中將「繪製器」切換為「軟繪製器」,以獲得更好的相容性。

View File

@ -389,9 +389,6 @@ fatal.mac_app_translocation=由于 macOS 的安全机制Hello Minecraft! Laun
fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即将完成升级,请重新打开 HMCL。\n如遇到问题你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即将完成升级,请重新打开 HMCL。\n如遇到问题你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
fatal.apply_update_failure=我们很抱歉 Hello Minecraft! Launcher 无法自动完成升级,因为出现了一些问题。\n但你依可以从 %s 手动下载 HMCL 来完成升级。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 fatal.apply_update_failure=我们很抱歉 Hello Minecraft! Launcher 无法自动完成升级,因为出现了一些问题。\n但你依可以从 %s 手动下载 HMCL 来完成升级。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
fatal.apply_update_need_win7=Hello Minecraft! Launcher 无法在 Windows XP/Vista 上进行自动更新。请从 %s 手动下载 HMCL 来完成升级。 fatal.apply_update_need_win7=Hello Minecraft! Launcher 无法在 Windows XP/Vista 上进行自动更新。请从 %s 手动下载 HMCL 来完成升级。
fatal.deprecated_java_version=HMCL 未来需要 Java 11 或更高版本才能运行,但依然支持使用 Java 8 启动游戏。建议安装最新版本的 Java 以确保 HMCL 能正常工作。\n你可以继续保留旧版本 Java。HMCL 能够识别与管理多个 Java并会自动根据游戏版本为你选择合适的 Java。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
fatal.deprecated_java_version.update=更高版本的 HMCL 需要 Java 11 或更高版本才能运行,请安装最新版本的 Java 以确保 HMCL 能够完成升级。\n你可以继续保留旧版本 Java。HMCL 能够识别与管理多个 Java并会自动根据游戏版本为你选择合适的 Java。
fatal.deprecated_java_version.download_link=下载 Java %d
fatal.samba=如果你正在通过 Samba 共享的文件夹中运行 Hello Minecraft! Launcher启动器可能无法正常工作。请尝试更新你的 Java 或在本地文件夹内运行 HMCL。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 fatal.samba=如果你正在通过 Samba 共享的文件夹中运行 Hello Minecraft! Launcher启动器可能无法正常工作。请尝试更新你的 Java 或在本地文件夹内运行 HMCL。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
fatal.illegal_char=由于你的用户文件夹路径中存在非法字符“=”,你将无法使用外置登录账户以及离线登录更换皮肤功能。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。 fatal.illegal_char=由于你的用户文件夹路径中存在非法字符“=”,你将无法使用外置登录账户以及离线登录更换皮肤功能。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
fatal.unsupported_platform=Minecraft 尚未对你的平台提供完善支持,所以可能影响游戏体验或无法启动游戏。\n若无法启动 Minecraft 1.17 及更高版本,可以尝试在“(全局/版本特定) 游戏设置 → 高级设置 → 调试选项”中将“渲染器”切换为“软渲染器”,以获得更好的兼容性。\n如遇到问题你可以点击右上角帮助按钮进行求助。 fatal.unsupported_platform=Minecraft 尚未对你的平台提供完善支持,所以可能影响游戏体验或无法启动游戏。\n若无法启动 Minecraft 1.17 及更高版本,可以尝试在“(全局/版本特定) 游戏设置 → 高级设置 → 调试选项”中将“渲染器”切换为“软渲染器”,以获得更好的兼容性。\n如遇到问题你可以点击右上角帮助按钮进行求助。

View File

@ -0,0 +1,7 @@
plugins {
id("java-library")
}
tasks.withType<JavaCompile> {
options.release.set(8)
}

View File

@ -0,0 +1,38 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl;
import org.jackhuang.hmcl.util.UTF8Control;
import java.util.ResourceBundle;
final class BootProperties {
private static ResourceBundle resourceBundle;
static ResourceBundle getResourceBundle() {
if (resourceBundle == null) {
resourceBundle = ResourceBundle.getBundle("assets.lang.boot", new UTF8Control());
}
return resourceBundle;
}
private BootProperties() {
}
}

View File

@ -0,0 +1,30 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl;
/**
* This is a dummy class and will be overwritten in the shadow jar.
*/
public final class EntryPoint {
private EntryPoint() {
}
public static void main(String[] args) {
throw new AssertionError("This method should not be called, please verify that the HMCL is complete.");
}
}

View File

@ -0,0 +1,73 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl;
import org.jackhuang.hmcl.util.SwingUtils;
/**
* @author Glavo
*/
public final class Main {
private static final int MINIMUM_JAVA_VERSION = 11;
private Main() {
}
/**
* Check if the current Java version is compatible with HMCL.
*/
static boolean checkJavaVersion(String javaVersion) {
if (javaVersion == null) {
return false;
}
try {
int major;
int dot = javaVersion.indexOf('.');
if (dot >= 0) {
major = Integer.parseInt(javaVersion.substring(0, dot));
if (major == 1 && dot < javaVersion.length() - 1) {
int begin = dot + 1;
dot = javaVersion.indexOf('.', begin);
major = dot > begin
? Integer.parseInt(javaVersion.substring(begin, dot))
: Integer.parseInt(javaVersion.substring(begin));
}
} else {
major = Integer.parseInt(javaVersion);
}
return major >= MINIMUM_JAVA_VERSION;
} catch (NumberFormatException e) {
return false;
}
}
public static void main(String[] args) throws Throwable {
if (checkJavaVersion(System.getProperty("java.version"))) {
EntryPoint.main(args);
} else {
String errorMessage = BootProperties.getResourceBundle().getString("boot.unsupported_java_version");
System.err.println(errorMessage);
SwingUtils.showErrorDialog(errorMessage);
System.exit(1);
}
}
}

View File

@ -1,7 +1,27 @@
package org.jackhuang.hmcl.ui; /*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util;
import javax.swing.*; import javax.swing.*;
/**
* @author Glavo
*/
public final class SwingUtils { public final class SwingUtils {
static { static {
if (System.getProperty("swing.defaultlaf") == null) { if (System.getProperty("swing.defaultlaf") == null) {

View File

@ -1,6 +1,6 @@
/* /*
* Hello Minecraft! Launcher * Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.jackhuang.hmcl.util.i18n; package org.jackhuang.hmcl.util;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -28,11 +28,9 @@ import java.util.ResourceBundle;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
final class UTF8Control extends ResourceBundle.Control { public final class UTF8Control extends ResourceBundle.Control {
public static final UTF8Control INSTANCE = new UTF8Control(); public UTF8Control() {}
private UTF8Control() {}
@Override @Override
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IOException { public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IOException {

View File

@ -0,0 +1,19 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
boot.unsupported_java_version=HMCL requires Java 11 or later to run, but still supports launching games with Java 8. Please install the latest version of Java and try opening HMCL again.\nYou can keep your old version of Java. HMCL can detect and manage multiple Java installations, and will automatically select the appropriate Java version for your game.

View File

@ -0,0 +1,19 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
boot.unsupported_java_version=HMCL 需要 Java 11 或更高版本才能執行,但依然支援使用 Java 8 啟動遊戲。請安裝最新版本的 Java 再嘗試開啟 HMCL。\n你可以繼續保留舊版本 Java。HMCL 能夠識別與管理多個 Java並會自動根據遊戲版本為你選取合適的 Java。

View File

@ -0,0 +1,19 @@
#
# Hello Minecraft! Launcher
# Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
boot.unsupported_java_version=HMCL 需要 Java 11 或更高版本才能运行,但依然支持使用 Java 8 启动游戏。请安装最新版本的 Java 再尝试启动 HMCL。\n你可以继续保留旧版本 Java。HMCL 能够识别与管理多个 Java并会自动根据游戏版本为你选择合适的 Java。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。

View File

@ -0,0 +1,39 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class MainTest {
@Test
public void testCheckJavaVersion() {
assertFalse(Main.checkJavaVersion("1.6.0"));
assertFalse(Main.checkJavaVersion("1.6.0_45"));
assertFalse(Main.checkJavaVersion("1.7.0"));
assertFalse(Main.checkJavaVersion("1.7.0_80"));
assertFalse(Main.checkJavaVersion("1.8"));
assertFalse(Main.checkJavaVersion("1.8.0_321"));
assertTrue(Main.checkJavaVersion("11"));
assertTrue(Main.checkJavaVersion("11.0.26"));
assertTrue(Main.checkJavaVersion("21"));
}
}

View File

@ -2,6 +2,15 @@ plugins {
`java-library` `java-library`
} }
tasks.withType<JavaCompile> {
sourceCompatibility = "11"
targetCompatibility = "11"
}
tasks.compileJava {
options.compilerArgs.add("--add-exports=jdk.attach/sun.tools.attach=ALL-UNNAMED")
}
dependencies { dependencies {
api(libs.kala.compress.zip) api(libs.kala.compress.zip)
api(libs.kala.compress.tar) api(libs.kala.compress.tar)

View File

@ -1,27 +1,38 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.logging; package org.jackhuang.hmcl.util.logging;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
/** /**
* @author Glavo * @author Glavo
*/ */
final class CallerFinder { final class CallerFinder {
private static final String PACKAGE_PREFIX = CallerFinder.class.getPackageName() + ".";
private static final Predicate<StackWalker.StackFrame> PREDICATE = stackFrame -> !stackFrame.getClassName().startsWith(PACKAGE_PREFIX);
private static final Function<Stream<StackWalker.StackFrame>, Optional<StackWalker.StackFrame>> FUNCTION = stream -> stream.filter(PREDICATE).findFirst();
private static final Function<StackWalker.StackFrame, String> FRAME_MAPPING = frame -> frame.getClassName() + "." + frame.getMethodName();
static String getCaller() { static String getCaller() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); return StackWalker.getInstance().walk(FUNCTION).map(FRAME_MAPPING).orElse(null);
int i = 0;
while (i++ < stackTrace.length) {
if (stackTrace[i].getClassName().equals(Logger.CLASS_NAME))
break;
}
while (i++ < stackTrace.length) {
StackTraceElement element = stackTrace[i];
String cname = element.getClassName();
if (!cname.equals(Logger.CLASS_NAME) && !cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) {
return cname + '.' + element.getMethodName();
}
}
return null;
} }
private CallerFinder() { private CallerFinder() {

View File

@ -24,9 +24,6 @@ subprojects {
} }
tasks.withType<JavaCompile> { tasks.withType<JavaCompile> {
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
options.encoding = "UTF-8" options.encoding = "UTF-8"
} }

View File

@ -2,6 +2,7 @@ rootProject.name = "HMCL3"
include( include(
"HMCL", "HMCL",
"HMCLCore", "HMCLCore",
"HMCLBoot",
"HMCLTransformerDiscoveryService" "HMCLTransformerDiscoveryService"
) )