diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index 21a106493..cbc14e5af 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -108,6 +108,50 @@ tasks.compileJava { options.compilerArgs.add("--add-exports=java.base/jdk.internal.loader=ALL-UNNAMED") } +val hmclProperties = buildList { + add("hmcl.version" to project.version.toString()) + System.getenv("GITHUB_SHA")?.let { + add("hmcl.version.hash" to it) + } + add("hmcl.version.type" to versionType) + add("hmcl.microsoft.auth.id" to microsoftAuthId) + add("hmcl.microsoft.auth.secret" to microsoftAuthSecret) + add("hmcl.curseforge.apikey" to curseForgeApiKey) + add("hmcl.authlib-injector.version" to libs.authlib.injector.get().version!!) +} + +val hmclPropertiesFile = layout.buildDirectory.file("hmcl.properties") +val createPropertiesFile by tasks.registering { + outputs.file(hmclPropertiesFile) + hmclProperties.forEach { (k, v) -> inputs.property(k, v) } + + doLast { + val targetFile = hmclPropertiesFile.get().asFile + targetFile.parentFile.mkdir() + targetFile.bufferedWriter().use { + for ((k, v) in hmclProperties) { + it.write("$k=$v\n") + } + } + } +} + +val addOpens = listOf( + "java.base/java.lang", + "java.base/java.lang.reflect", + "java.base/jdk.internal.loader", + "javafx.base/com.sun.javafx.binding", + "javafx.base/com.sun.javafx.event", + "javafx.base/com.sun.javafx.runtime", + "javafx.graphics/javafx.css", + "javafx.graphics/com.sun.javafx.stage", + "javafx.graphics/com.sun.prism", + "javafx.controls/com.sun.javafx.scene.control", + "javafx.controls/com.sun.javafx.scene.control.behavior", + "javafx.controls/javafx.scene.control.skin", + "jdk.attach/sun.tools.attach", +) + tasks.jar { enabled = false dependsOn(tasks["shadowJar"]) @@ -116,6 +160,8 @@ tasks.jar { val jarPath = tasks.jar.get().archiveFile.get().asFile tasks.shadowJar { + dependsOn(createPropertiesFile) + archiveClassifier.set(null as String?) exclude("**/package-info.class") @@ -136,43 +182,14 @@ tasks.shadowJar { exclude(project(":HMCLBoot")) } - into("assets/") { - from(embedResources) - } - - manifest { - attributes( - "Created-By" to "Copyright(c) 2013-2025 huangyuhui.", - "Main-Class" to "org.jackhuang.hmcl.Main", - "Multi-Release" to "true", - "Implementation-Version" to project.version, - "Microsoft-Auth-Id" to microsoftAuthId, - "Microsoft-Auth-Secret" to microsoftAuthSecret, - "CurseForge-Api-Key" to curseForgeApiKey, - "Authlib-Injector-Version" to libs.authlib.injector.get().version!!, - "Build-Channel" to versionType, - "Add-Opens" to listOf( - "java.base/java.lang", - "java.base/java.lang.reflect", - "java.base/jdk.internal.loader", - "javafx.base/com.sun.javafx.binding", - "javafx.base/com.sun.javafx.event", - "javafx.base/com.sun.javafx.runtime", - "javafx.graphics/javafx.css", - "javafx.graphics/com.sun.javafx.stage", - "javafx.graphics/com.sun.prism", - "javafx.controls/com.sun.javafx.scene.control", - "javafx.controls/com.sun.javafx.scene.control.behavior", - "javafx.controls/javafx.scene.control.skin", - "jdk.attach/sun.tools.attach", - ).joinToString(" "), - "Enable-Native-Access" to "ALL-UNNAMED" - ) - - System.getenv("GITHUB_SHA")?.also { - attributes("GitHub-SHA" to it) - } - } + manifest.attributes( + "Created-By" to "Copyright(c) 2013-2025 huangyuhui.", + "Implementation-Version" to project.version.toString(), + "Main-Class" to "org.jackhuang.hmcl.Main", + "Multi-Release" to "true", + "Add-Opens" to addOpens.joinToString(" "), + "Enable-Native-Access" to "ALL-UNNAMED" + ) if (launcherExe != null) { into("assets") { @@ -186,6 +203,15 @@ tasks.shadowJar { } } +tasks.processResources { + dependsOn(createPropertiesFile) + + into("assets/") { + from(hmclPropertiesFile) + from(embedResources) + } +} + val makeExecutables by tasks.registering { val extensions = listOf("exe", "sh") @@ -268,6 +294,16 @@ fun parseToolOptions(options: String?): MutableList { return result } +// For IntelliJ IDEA +tasks.withType { + if (name != "run") { + jvmArgs(addOpens.map { "--add-opens=$it=ALL-UNNAMED" }) + if (javaVersion >= JavaVersion.VERSION_24) { + jvmArgs("--enable-native-access=ALL-UNNAMED") + } + } +} + tasks.register("run") { dependsOn(tasks.jar) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java index 2e630a942..75a9fb57b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java @@ -33,7 +33,7 @@ public final class Metadata { public static final String NAME = "HMCL"; 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.getAttribute("hmcl.version", "@develop@")); public static final String TITLE = NAME + " " + VERSION; public static final String FULL_TITLE = FULL_NAME + " v" + VERSION; @@ -49,8 +49,8 @@ public final class Metadata { public static final String EULA_URL = DOCS_URL + "/eula/hmcl.html"; public static final String GROUPS_URL = "https://www.bilibili.com/opus/905435541874409529"; - public static final String BUILD_CHANNEL = JarUtils.getManifestAttribute("Build-Channel", "nightly"); - public static final String GITHUB_SHA = JarUtils.getManifestAttribute("GitHub-SHA", null); + public static final String BUILD_CHANNEL = JarUtils.getAttribute("hmcl.version.type", "nightly"); + public static final String GITHUB_SHA = JarUtils.getAttribute("hmcl.version.hash", null); public static final Path CURRENT_DIRECTORY = Paths.get(System.getProperty("user.dir")).toAbsolutePath().normalize(); public static final Path MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory("minecraft"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java index de7849c3a..ebd055c3a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -158,13 +158,13 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session { @Override public String getClientId() { return System.getProperty("hmcl.microsoft.auth.id", - JarUtils.getManifestAttribute("Microsoft-Auth-Id", "")); + JarUtils.getAttribute("hmcl.microsoft.auth.id", "")); } @Override public String getClientSecret() { return System.getProperty("hmcl.microsoft.auth.secret", - JarUtils.getManifestAttribute("Microsoft-Auth-Secret", "")); + JarUtils.getAttribute("hmcl.microsoft.auth.secret", "")); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index 97379cf75..edc1d4e33 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -363,9 +363,9 @@ public final class Accounts { return new SimpleAuthlibInjectorArtifactProvider(Paths.get(authlibinjectorLocation)); } - String authlibInjectorVersion = JarUtils.getManifestAttribute("Authlib-Injector-Version", null); + String authlibInjectorVersion = JarUtils.getAttribute("hmcl.authlib-injector.version", null); if (authlibInjectorVersion == null) - throw new AssertionError("Missing Authlib-Injector-Version"); + throw new AssertionError("Missing hmcl.authlib-injector.version"); String authlibInjectorFileName = "authlib-injector-" + authlibInjectorVersion + ".jar"; return new AuthlibInjectorExtractor(Accounts.class.getResource("/assets/" + authlibInjectorFileName), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java index a5c762a62..074687eaa 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java @@ -46,7 +46,6 @@ import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.ChecksumMismatchException; import org.jackhuang.hmcl.util.io.IOUtils; -import org.jackhuang.hmcl.util.io.JarUtils; import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.Platform; @@ -63,6 +62,7 @@ import java.util.List; import java.util.*; import java.util.concurrent.CancellationException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.jar.Manifest; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.toSet; @@ -261,9 +261,15 @@ public final class SelfDependencyPatcher { .map(DependencyDescriptor::localPath) .toArray(Path[]::new); - String[] addOpens = JarUtils.getManifestAttribute("Add-Opens", "").split(" "); + String addOpens = null; + try (InputStream input = SelfDependencyPatcher.class.getResourceAsStream("/META-INF/MANIFEST.MF")) { + if (input != null) + addOpens = new Manifest(input).getMainAttributes().getValue("Add-Opens"); + } catch (IOException e) { + LOG.warning("Failed to read MANIFEST.MF file", e); + } - JavaFXPatcher.patch(modules, jars, addOpens); + JavaFXPatcher.patch(modules, jars, addOpens != null ? addOpens.split(" ") : new String[0]); } /** diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java index e9cf35773..9ad325848 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java @@ -45,7 +45,7 @@ import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; public final class CurseForgeRemoteModRepository implements RemoteModRepository { private static final String PREFIX = "https://api.curseforge.com"; - private static final String apiKey = System.getProperty("hmcl.curseforge.apikey", JarUtils.getManifestAttribute("CurseForge-Api-Key", "")); + private static final String apiKey = System.getProperty("hmcl.curseforge.apikey", JarUtils.getAttribute("hmcl.curseforge.apikey", "")); private static final int WORD_PERFECT_MATCH_WEIGHT = 5; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/JarUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/JarUtils.java index 548aa32da..78e4f5761 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/JarUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/JarUtils.java @@ -20,47 +20,51 @@ package org.jackhuang.hmcl.util.io; import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.nio.file.FileSystemNotFoundException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.security.CodeSource; -import java.util.jar.JarFile; -import java.util.jar.Manifest; +import java.util.Properties; public final class JarUtils { private JarUtils() { } private static final Path THIS_JAR; - - private static final Manifest manifest; + private static final Properties properties = new Properties(); static { - CodeSource cs = JarUtils.class.getProtectionDomain().getCodeSource(); + Class entryPointClass = null; + CodeSource cs = null; + try { + entryPointClass = Class.forName("org.jackhuang.hmcl.EntryPoint"); + cs = entryPointClass.getProtectionDomain().getCodeSource(); + } catch (ClassNotFoundException ignored) { + } + if (cs == null) { THIS_JAR = null; - manifest = new Manifest(); } else { Path path; try { - path = Paths.get(cs.getLocation().toURI()).toAbsolutePath(); + path = Path.of(cs.getLocation().toURI()).toAbsolutePath(); } catch (FileSystemNotFoundException | IllegalArgumentException | URISyntaxException e) { path = null; } - if (path == null || !Files.isRegularFile(path)) { - THIS_JAR = null; - manifest = new Manifest(); - } else { - THIS_JAR = path; - Manifest mn; - try (JarFile file = new JarFile(path.toFile())) { - mn = file.getManifest(); - } catch (IOException e) { - mn = new Manifest(); + THIS_JAR = path != null && Files.isRegularFile(path) ? path : null; + } + + if (entryPointClass != null) { + InputStream input = entryPointClass.getResourceAsStream("/assets/hmcl.properties"); + if (input != null) { + try (var reader = new InputStreamReader(input, StandardCharsets.UTF_8)) { + properties.load(reader); + } catch (IOException ignored) { } - manifest = mn; } } } @@ -70,8 +74,7 @@ public final class JarUtils { return THIS_JAR; } - public static String getManifestAttribute(String name, String defaultValue) { - String value = manifest.getMainAttributes().getValue(name); - return value != null ? value : defaultValue; + public static String getAttribute(String name, String defaultValue) { + return properties.getProperty(name, defaultValue); } }