From 451baf0ea1db62ed31daf7794757f2e999fac3c7 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:38:06 +0800 Subject: [PATCH 01/34] =?UTF-8?q?=E4=BB=8A=E5=A4=9C=E8=8B=A5=E6=AC=A0?= =?UTF-8?q?=E6=B8=85=E5=85=89=E6=BB=A1=E5=A0=82=20=E8=98=B8=E7=AC=94?= =?UTF-8?q?=E5=A4=A9=E6=B2=B3=E7=BB=98=E4=B8=87=E5=8F=A4=E7=94=9F=E9=A6=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + .../jackhuang/hmcl/game/HMCLGameLauncher.java | 51 +++++- HMCLCore/build.gradle.kts | 3 +- .../download/forge/ForgeBMCLVersionList.java | 2 +- .../hmcl/download/forge/ForgeInstallTask.java | 46 ++++-- .../download/forge/ForgeInstallerType.java | 24 +++ .../forge/ForgeLegacyInstallTask.java | 101 ++++++++++++ .../HMCLLegacyForgeHelper/build.gradle.kts | 18 +++ .../org/jackhuang/hmcl/LegacyForgeHelper.java | 153 ++++++++++++++++++ .../HMCLMultiMCBootstrap/build.gradle.kts | 2 +- settings.gradle.kts | 2 +- 11 files changed, 379 insertions(+), 25 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java create mode 100644 minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts create mode 100644 minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java diff --git a/.gitignore b/.gitignore index 0cff8dbffc7..b4550b92488 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ language-subtag-registry /HMCLBoot/build/ /minecraft/libraries/HMCLTransformerDiscoveryService/build/ /minecraft/libraries/HMCLMultiMCBootstrap/build/ +/minecraft/libraries/HMCLLegacyForgeHelper/build/ /buildSrc/build/ # idea @@ -42,6 +43,7 @@ language-subtag-registry /HMCLCore/out/ /minecraft/libraries/HMCLTransformerDiscoveryService/out/ /minecraft/libraries/HMCLMultiMCBootstrap/out/ +/minecraft/libraries/HMCLLegacyForgeHelper/out/ # eclipse /bin/ diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java index e13cd3d5d31..1b395074b8d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java @@ -31,8 +31,11 @@ import java.io.IOException; import java.io.InputStream; -import java.nio.file.*; -import java.util.*; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Locale; +import java.util.Map; import java.util.stream.Stream; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -169,9 +172,8 @@ public void makeLaunchScript(Path scriptFile) throws IOException { protected void appendJvmArgs(CommandBuilder result) { super.appendJvmArgs(result); - if (options.isAllowAutoAgent() - && !options.isNoGeneratedJVMArgs() - && !options.isNoGeneratedOptimizingJVMArgs() + if (!options.isAllowAutoAgent() && !options.isNoGeneratedJVMArgs()) return; + if (!options.isNoGeneratedOptimizingJVMArgs() && NativePatcher.needPatchMemoryUtil(manifest, options.getJava().getParsedVersion())) { LOG.info("Attempting to patch game with lwjgl-unsafe-agent"); try { @@ -179,6 +181,13 @@ protected void appendJvmArgs(CommandBuilder result) { } catch (Exception e) { LOG.warning("Failed to extract lwjgl-unsafe-agent", e); } + } else if (this.instance.getVersion().compareTo("1.6") < 0 && this.instance.hasComponent(GameComponentType.FORGE)) { + LOG.info("Attempting to patch game with legacy-forge-helper"); + try { + result.add("-javaagent:" + extractLegacyForgeHelper()); + } catch (Exception e) { + LOG.warning("Failed to extract legacy-forge-helper", e); + } } } @@ -220,4 +229,36 @@ private Path extractLwjglUnsafeAgent() throws IOException { return agentPath; } + private Path extractLegacyForgeHelper() throws IOException { + Library library = new Library(new Artifact("org.jackhuang.hmcl", "legacy-forge-helper", "1.0")); + String fileName = "HMCLLegacyForgeHelper-1.0.jar"; + + Path agentPath = instance.getLayout().getLibraryFile(instance.getId(), library).toAbsolutePath().normalize(); + if (agentPath.toString().contains("=")) { + throw new IOException("Invalid library path: " + agentPath); + } + + byte[] bytes; + try (InputStream input = DefaultLauncher.class.getResourceAsStream("/assets/game/" + fileName)) { + if (input == null) { + throw new IOException("/assets/game/" + fileName + " not found"); + } + + bytes = input.readAllBytes(); + } + + if (Files.isRegularFile(agentPath)) { + try { + if (Files.size(agentPath) == bytes.length) { + return agentPath; + } + } catch (IOException e) { + LOG.warning("Failed to check size of " + agentPath, e); + } + } + + Files.createDirectories(agentPath.getParent()); + FileUtils.saveSafely(agentPath, output -> output.write(bytes)); + return agentPath; + } } diff --git a/HMCLCore/build.gradle.kts b/HMCLCore/build.gradle.kts index 8d108d24904..7de04b35418 100644 --- a/HMCLCore/build.gradle.kts +++ b/HMCLCore/build.gradle.kts @@ -44,7 +44,8 @@ dependencies { tasks.processResources { listOf( "HMCLTransformerDiscoveryService", - "HMCLMultiMCBootstrap" + "HMCLMultiMCBootstrap", + "HMCLLegacyForgeHelper" ).map { project(":$it").tasks["jar"] as Jar }.forEach { task -> dependsOn(task) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java index 14dbcaae3d8..33baccc25f2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java @@ -91,7 +91,7 @@ public Task refreshAsync(String gameVersion) { continue; List urls = new ArrayList<>(); for (ForgeVersion.File file : version.getFiles()) - if ("installer".equals(file.getCategory()) && "jar".equals(file.getFormat())) { + if (("installer".equals(file.getCategory()) && "jar".equals(file.getFormat())) || (("client".equals(file.getCategory()) || "universal".equals(file.getCategory())) && "zip".equals(file.getFormat()))) { String branch = toLookupBranch(gameVersion, version.getBranch()); String classifier = lookupVersion + "-" + version.getVersion() + (branch.isEmpty() ? "" : '-' + branch); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java index 8b6e089659f..6ad220ae6d6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -17,7 +17,9 @@ */ package org.jackhuang.hmcl.download.forge; -import org.jackhuang.hmcl.download.*; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.UnsupportedInstallationException; +import org.jackhuang.hmcl.download.VersionMismatchException; import org.jackhuang.hmcl.download.game.GameDownloadTask; import org.jackhuang.hmcl.game.GameComponentAnalyzer; import org.jackhuang.hmcl.game.GameComponentType; @@ -109,16 +111,24 @@ public void execute() throws IOException, VersionMismatchException, UnsupportedI throw new UnsupportedInstallationException(UNSUPPORTED_LAUNCH_WRAPPER); } - if (detectForgeInstallerType(remote.getGameVersion(), installer)) { - dependency = new GameDownloadTask(dependencyManager, manifest) - .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( - dependencyManager, - manifest, - minecraftJar, - remote.getSelfVersion(), - installer)); - } else { - dependency = new ForgeOldInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); + var type = detectForgeInstallerType(remote.getGameVersion(), installer); + + switch (type) { + case LEGACY: + dependency = new ForgeLegacyInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); + break; + case NORMAL: + dependency = new ForgeOldInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); + break; + case MODERN: + dependency = new GameDownloadTask(dependencyManager, manifest) + .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( + dependencyManager, + manifest, + minecraftJar, + remote.getSelfVersion(), + installer)); + break; } } @@ -130,20 +140,24 @@ public void execute() throws IOException, VersionMismatchException, UnsupportedI /// @throws IOException if the installer profile is missing, malformed, or /// unsupported /// @throws VersionMismatchException if the installer targets another Minecraft version - public static boolean detectForgeInstallerType(String gameVersion, Path installer) throws IOException, VersionMismatchException { + public static ForgeInstallerType detectForgeInstallerType(String gameVersion, Path installer) throws IOException, VersionMismatchException { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { + if ((Files.isRegularFile(fs.getPath("mod_MinecraftForge.class")) || Files.isRegularFile(fs.getPath("fmlversion.properties"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { + return ForgeInstallerType.LEGACY; + } + String installProfileText = Files.readString(fs.getPath("install_profile.json")); Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); if (installProfile.containsKey("spec")) { ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); if (!gameVersion.equals(profile.getMinecraft())) throw new VersionMismatchException(profile.getMinecraft(), gameVersion); - return true; + return ForgeInstallerType.MODERN; } else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) { ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class); if (!gameVersion.equals(profile.install().getMinecraft())) throw new VersionMismatchException(profile.install().getMinecraft(), gameVersion); - return false; + return ForgeInstallerType.NORMAL; } else { throw new IOException(); } @@ -160,9 +174,9 @@ public static boolean detectForgeInstallerType(String gameVersion, Path installe /// @param gameVersion Minecraft version expected by the installation /// @param installer the Forge installer JAR /// @return the task producing the Forge patch - /// @throws IOException if the installer profile is missing, malformed, or + /// @throws IOException if the installer profile is missing, malformed, or /// unsupported - /// @throws VersionMismatchException if the installer targets another Minecraft version + /// @throws VersionMismatchException if the installer targets another Minecraft version /// @throws UnsupportedInstallationException if the manifest already contains Cleanroom public static Task install( DefaultDependencyManager dependencyManager, diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java new file mode 100644 index 00000000000..7783ad832e7 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java @@ -0,0 +1,24 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui 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 . + */ +package org.jackhuang.hmcl.download.forge; + +public enum ForgeInstallerType { + LEGACY, + NORMAL, + MODERN +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java new file mode 100644 index 00000000000..391dd7561ee --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java @@ -0,0 +1,101 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui 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 . + */ +package org.jackhuang.hmcl.download.forge; + +import org.jackhuang.hmcl.download.ArtifactMalformedException; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.game.*; +import org.jackhuang.hmcl.task.Task; + +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +public class ForgeLegacyInstallTask extends Task { + + private final DefaultDependencyManager dependencyManager; + private final GameInstanceManifest manifest; + private final Path installer; + private final String selfVersion; + private final List> dependencies = new ArrayList<>(1); + + ForgeLegacyInstallTask(DefaultDependencyManager dependencyManager, GameInstanceManifest manifest, String selfVersion, Path installer) { + this.dependencyManager = dependencyManager; + this.manifest = manifest; + this.installer = installer; + this.selfVersion = selfVersion; + + setSignificance(TaskSignificance.MAJOR); + } + + @Override + public List> getDependencies() { + return dependencies; + } + + @Override + public boolean doPreExecute() { + return true; + } + + @Override + public void execute() throws Exception { + try (ZipFile zipFile = new ZipFile(installer.toFile())) { + ZipEntry entry1 = zipFile.getEntry("fmlversion.properties"); + ZipEntry entry2 = zipFile.getEntry("mod_MinecraftForge.class"); + + InputStream stream = null; + InputStream stream2 = null; + + if (entry1 != null) { + stream = zipFile.getInputStream(entry1); + } + if (entry2 != null) { + stream2 = zipFile.getInputStream(entry2); + } + + if (stream == null && stream2 == null) { + throw new ArtifactMalformedException("Malformed forge installer file, forgeversion.properties and mod_MinecraftForge.class both does not exist."); + } + + // unpack the universal jar in the installer file. + Library forgeLibrary = new Library(new Artifact( + "net.minecraftforge", "forge", selfVersion + )); + GameRepository gameRepository = dependencyManager.getGameRepository(); + Path forgeFile = gameRepository.getLayout().getLibraryFile(manifest.id(), forgeLibrary); + Files.createDirectories(forgeFile.getParent()); + + try (InputStream is = Files.newInputStream(installer); + OutputStream os = Files.newOutputStream(forgeFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + is.transferTo(os); + } + + setResult(new GameInstancePatch(GameComponentType.FORGE.getPatchId(), selfVersion, GameInstancePatch.PRIORITY_LOADER, null, null, List.of(forgeLibrary))); + } catch (ZipException ex) { + throw new ArtifactMalformedException("Malformed forge installer file", ex); + } + } +} diff --git a/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts b/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts new file mode 100644 index 00000000000..6f12a5a1b33 --- /dev/null +++ b/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts @@ -0,0 +1,18 @@ +version = "1.0" + +tasks.compileJava { + sourceCompatibility = "1.8" + targetCompatibility = "1.8" +} + +tasks.jar { + manifest { + attributes( + "Created-By" to "Copyright(c) 2026 huangyuhui.", + "Implementation-Version" to project.version, + "Premain-Class" to "org.jackhuang.hmcl.LegacyForgeHelper", + "Can-Redefine-Classes" to true, + "Can-Retransform-Classes" to true + ) + } +} diff --git a/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java b/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java new file mode 100644 index 00000000000..989caa32c59 --- /dev/null +++ b/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java @@ -0,0 +1,153 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui 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 . + */ +package org.jackhuang.hmcl; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.nio.charset.StandardCharsets; +import java.security.ProtectionDomain; + +@SuppressWarnings("JavaPrintToLogpoint") +public class LegacyForgeHelper { + private static final String TARGET_URL = "http://files.minecraftforge.net/fmllibs/%s"; + + private static String newRootUrl = "https://files.multimc.org/fmllibs/%s"; + + public static void premain(String agentArgs, Instrumentation inst) { + if (agentArgs != null && !agentArgs.trim().isEmpty()) { + newRootUrl = agentArgs.trim(); + } + System.out.println("[FMLFixAgent] Loaded. Target RootURL will be replaced with: " + newRootUrl); + inst.addTransformer(new CoreFMLLibrariesTransformer()); + } + + public static void agentmain(String agentArgs, Instrumentation inst) { + premain(agentArgs, inst); + } + + private static class CoreFMLLibrariesTransformer implements ClassFileTransformer { + private static final String TARGET_CLASS = "cpw/mods/fml/relauncher/CoreFMLLibraries"; + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] classfileBuffer) { + if (className == null) { + return null; + } + + if (TARGET_CLASS.equals(className) || "cpw.mods.fml.relauncher.CoreFMLLibraries".equals(className)) { + try { + System.out.println("[FMLFixAgent] Transforming " + className + " ..."); + byte[] modified = patchConstantPoolUtf8(classfileBuffer, TARGET_URL, newRootUrl); + System.out.println("[FMLFixAgent] Successfully patched RootURL in " + className); + return modified; + } catch (Throwable t) { + System.err.println("[FMLFixAgent] Failed to patch class: " + t.getMessage()); + t.printStackTrace(); + } + } + return null; + } + } + + private static byte[] patchConstantPoolUtf8(byte[] classBytes, String targetStr, String replacementStr) { + if (classBytes == null || classBytes.length < 10) { + return classBytes; + } + + int pos = 0; + + int magic = ((classBytes[pos++] & 0xFF) << 24) | + ((classBytes[pos++] & 0xFF) << 16) | + ((classBytes[pos++] & 0xFF) << 8) | + (classBytes[pos++] & 0xFF); + if (magic != 0xCAFEBABE) { + throw new IllegalArgumentException("Invalid class file (magic mismatch)"); + } + + // minor_version & major_version + pos += 4; + + int cpCount = ((classBytes[pos++] & 0xFF) << 8) | (classBytes[pos++] & 0xFF); + + for (int i = 1; i < cpCount; i++) { + int tag = classBytes[pos++] & 0xFF; + switch (tag) { + case 1: // CONSTANT_Utf8 + int utf8LengthPos = pos; + int len = ((classBytes[pos++] & 0xFF) << 8) | (classBytes[pos++] & 0xFF); + String str = new String(classBytes, pos, len, StandardCharsets.UTF_8); + pos += len; + + if (targetStr.equals(str)) { + byte[] replacementBytes = replacementStr.getBytes(StandardCharsets.UTF_8); + if (replacementBytes.length > 65535) { + throw new IllegalArgumentException("Replacement URL is too long (max 65535 bytes)"); + } + + int beforeLen = utf8LengthPos; + int afterOffset = utf8LengthPos + 2 + len; + int afterLen = classBytes.length - afterOffset; + + byte[] newClass = new byte[beforeLen + 2 + replacementBytes.length + afterLen]; + + System.arraycopy(classBytes, 0, newClass, 0, beforeLen); + newClass[beforeLen] = (byte) ((replacementBytes.length >>> 8) & 0xFF); + newClass[beforeLen + 1] = (byte) (replacementBytes.length & 0xFF); + System.arraycopy(replacementBytes, 0, newClass, beforeLen + 2, replacementBytes.length); + System.arraycopy(classBytes, afterOffset, newClass, beforeLen + 2 + replacementBytes.length, afterLen); + + return newClass; + } + break; + + case 3: // CONSTANT_Integer + case 4: // CONSTANT_Float + case 9: // CONSTANT_Fieldref + case 10: // CONSTANT_Methodref + case 11: // CONSTANT_InterfaceMethodref + case 12: // CONSTANT_NameAndType + case 18: // CONSTANT_InvokeDynamic + pos += 4; + break; + + case 5: // CONSTANT_Long + case 6: // CONSTANT_Double + pos += 8; + i++; + break; + + case 7: // CONSTANT_Class + case 8: // CONSTANT_String + case 16: // CONSTANT_MethodType + pos += 2; + break; + + case 15: // CONSTANT_MethodHandle + pos += 3; + break; + + default: + throw new IllegalArgumentException("Unsupported constant pool tag: " + tag + " at pos " + (pos - 1)); + } + } + + System.out.println("[FMLFixAgent] Warning: Target URL string not found in constant pool."); + return classBytes; + } +} diff --git a/minecraft/libraries/HMCLMultiMCBootstrap/build.gradle.kts b/minecraft/libraries/HMCLMultiMCBootstrap/build.gradle.kts index 08e0c9f2834..979ad9c808d 100644 --- a/minecraft/libraries/HMCLMultiMCBootstrap/build.gradle.kts +++ b/minecraft/libraries/HMCLMultiMCBootstrap/build.gradle.kts @@ -8,7 +8,7 @@ tasks.compileJava { tasks.jar { manifest { attributes( - "Created-By" to "Copyright(c) 2013-2025 huangyuhui.", + "Created-By" to "Copyright(c) 2026 huangyuhui.", "Implementation-Version" to project.version ) } diff --git a/settings.gradle.kts b/settings.gradle.kts index a1a710b39c0..c35fe1bc1d9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,7 +5,7 @@ include( "HMCLBoot" ) -val minecraftLibraries = listOf("HMCLTransformerDiscoveryService", "HMCLMultiMCBootstrap") +val minecraftLibraries = listOf("HMCLTransformerDiscoveryService", "HMCLMultiMCBootstrap", "HMCLLegacyForgeHelper") include(minecraftLibraries) for (library in minecraftLibraries) { From f13112f32ffcad497c2f2305405ab33afd4505d9 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:38:53 +0800 Subject: [PATCH 02/34] =?UTF-8?q?=E5=8F=B2=E7=AC=94=E6=BB=94=E6=BB=94?= =?UTF-8?q?=E7=83=9F=E9=9C=9E=E7=BB=A3=E5=83=8F=20=E5=B0=B1=E4=B8=AD?= =?UTF-8?q?=E6=8B=88=E6=9D=A5=E7=8E=B2=E7=8F=91=E5=8D=81=E4=BA=8C=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- minecraft/libraries/HMCLMultiMCBootstrap/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minecraft/libraries/HMCLMultiMCBootstrap/build.gradle.kts b/minecraft/libraries/HMCLMultiMCBootstrap/build.gradle.kts index 979ad9c808d..08e0c9f2834 100644 --- a/minecraft/libraries/HMCLMultiMCBootstrap/build.gradle.kts +++ b/minecraft/libraries/HMCLMultiMCBootstrap/build.gradle.kts @@ -8,7 +8,7 @@ tasks.compileJava { tasks.jar { manifest { attributes( - "Created-By" to "Copyright(c) 2026 huangyuhui.", + "Created-By" to "Copyright(c) 2013-2025 huangyuhui.", "Implementation-Version" to project.version ) } From 66fd48c52faac4bbf1e4a0053976e0610054c2af Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:50:28 +0800 Subject: [PATCH 03/34] =?UTF-8?q?=E5=90=B9=E5=BC=80=E6=B1=97=E9=9D=92?= =?UTF-8?q?=E6=B7=8B=E6=BC=93=E8=8B=8D=E8=8C=AB=20=E6=98=8E=E8=BE=A8?= =?UTF-8?q?=E6=AE=8B=E7=A8=BF=E8=B0=81=E7=A7=8B=E6=B0=B4=E4=B8=80=E5=8F=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jackhuang/hmcl/game/HMCLGameLauncher.java | 17 ++++++++++------- .../org/jackhuang/hmcl/LegacyForgeHelper.java | 10 +++++----- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java index 1b395074b8d..2bbfe1e75ee 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java @@ -172,7 +172,17 @@ public void makeLaunchScript(Path scriptFile) throws IOException { protected void appendJvmArgs(CommandBuilder result) { super.appendJvmArgs(result); + if (this.instance.getVersion().compareTo("1.6") < 0 && this.instance.hasComponent(GameComponentType.FORGE)) { + LOG.info("Attempting to patch game with legacy-forge-helper"); + try { + result.add("-javaagent:" + extractLegacyForgeHelper()); + } catch (Exception e) { + LOG.warning("Failed to extract legacy-forge-helper", e); + } + } + if (!options.isAllowAutoAgent() && !options.isNoGeneratedJVMArgs()) return; + if (!options.isNoGeneratedOptimizingJVMArgs() && NativePatcher.needPatchMemoryUtil(manifest, options.getJava().getParsedVersion())) { LOG.info("Attempting to patch game with lwjgl-unsafe-agent"); @@ -181,13 +191,6 @@ protected void appendJvmArgs(CommandBuilder result) { } catch (Exception e) { LOG.warning("Failed to extract lwjgl-unsafe-agent", e); } - } else if (this.instance.getVersion().compareTo("1.6") < 0 && this.instance.hasComponent(GameComponentType.FORGE)) { - LOG.info("Attempting to patch game with legacy-forge-helper"); - try { - result.add("-javaagent:" + extractLegacyForgeHelper()); - } catch (Exception e) { - LOG.warning("Failed to extract legacy-forge-helper", e); - } } } diff --git a/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java b/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java index 989caa32c59..c978cbc1153 100644 --- a/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java +++ b/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java @@ -32,7 +32,7 @@ public static void premain(String agentArgs, Instrumentation inst) { if (agentArgs != null && !agentArgs.trim().isEmpty()) { newRootUrl = agentArgs.trim(); } - System.out.println("[FMLFixAgent] Loaded. Target RootURL will be replaced with: " + newRootUrl); + System.out.println("[LegacyForgeHelper] Loaded. Target RootURL will be replaced with: " + newRootUrl); inst.addTransformer(new CoreFMLLibrariesTransformer()); } @@ -52,12 +52,12 @@ public byte[] transform(ClassLoader loader, String className, Class classBein if (TARGET_CLASS.equals(className) || "cpw.mods.fml.relauncher.CoreFMLLibraries".equals(className)) { try { - System.out.println("[FMLFixAgent] Transforming " + className + " ..."); + System.out.println("[LegacyForgeHelper] Transforming " + className + " ..."); byte[] modified = patchConstantPoolUtf8(classfileBuffer, TARGET_URL, newRootUrl); - System.out.println("[FMLFixAgent] Successfully patched RootURL in " + className); + System.out.println("[LegacyForgeHelper] Successfully patched RootURL in " + className); return modified; } catch (Throwable t) { - System.err.println("[FMLFixAgent] Failed to patch class: " + t.getMessage()); + System.err.println("[LegacyForgeHelper] Failed to patch class: " + t.getMessage()); t.printStackTrace(); } } @@ -147,7 +147,7 @@ private static byte[] patchConstantPoolUtf8(byte[] classBytes, String targetStr, } } - System.out.println("[FMLFixAgent] Warning: Target URL string not found in constant pool."); + System.out.println("[LegacyForgeHelper] Warning: Target URL string not found in constant pool."); return classBytes; } } From 1471ab145172441130fb8c4ac80c11459218b5ae Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:56:24 +0800 Subject: [PATCH 04/34] =?UTF-8?q?=E4=B9=A6=E9=98=81=E6=B7=B1=E5=A4=84?= =?UTF-8?q?=E7=AF=86=E9=9A=B6=E6=B5=81=E8=8A=B3=20=E6=96=AD=E7=AE=80?= =?UTF-8?q?=E9=81=97=E7=AF=87=E6=8C=A5=E6=AF=AB=E7=BB=AD=E5=85=B4=E4=BA=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/LegacyForgeHelper.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java b/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java index c978cbc1153..3cffab833a2 100644 --- a/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java +++ b/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java @@ -23,7 +23,11 @@ import java.security.ProtectionDomain; @SuppressWarnings("JavaPrintToLogpoint") -public class LegacyForgeHelper { +public final class LegacyForgeHelper { + private LegacyForgeHelper () { + throw new AssertionError(); + } + private static final String TARGET_URL = "http://files.minecraftforge.net/fmllibs/%s"; private static String newRootUrl = "https://files.multimc.org/fmllibs/%s"; @@ -40,12 +44,11 @@ public static void agentmain(String agentArgs, Instrumentation inst) { premain(agentArgs, inst); } - private static class CoreFMLLibrariesTransformer implements ClassFileTransformer { + private final static class CoreFMLLibrariesTransformer implements ClassFileTransformer { private static final String TARGET_CLASS = "cpw/mods/fml/relauncher/CoreFMLLibraries"; @Override - public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, - ProtectionDomain protectionDomain, byte[] classfileBuffer) { + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { if (className == null) { return null; } @@ -72,10 +75,7 @@ private static byte[] patchConstantPoolUtf8(byte[] classBytes, String targetStr, int pos = 0; - int magic = ((classBytes[pos++] & 0xFF) << 24) | - ((classBytes[pos++] & 0xFF) << 16) | - ((classBytes[pos++] & 0xFF) << 8) | - (classBytes[pos++] & 0xFF); + int magic = ((classBytes[pos++] & 0xFF) << 24) | ((classBytes[pos++] & 0xFF) << 16) | ((classBytes[pos++] & 0xFF) << 8) | (classBytes[pos++] & 0xFF); if (magic != 0xCAFEBABE) { throw new IllegalArgumentException("Invalid class file (magic mismatch)"); } From d2e904e1445ea4fd373b3bfaf8d469da707612a7 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Thu, 27 Aug 2026 21:42:05 +0800 Subject: [PATCH 05/34] =?UTF-8?q?=E8=B0=81=E7=99=BD=E8=8D=89=E8=83=A1?= =?UTF-8?q?=E7=AC=B3=E7=83=BD=E7=87=A7=E9=95=BF=20=E5=B0=86=E5=8D=8A?= =?UTF-8?q?=E7=94=9F=E5=93=80=E6=AF=81=E4=BB=A5=E8=AF=97=E6=83=85=E6=8A=B5?= =?UTF-8?q?=E6=8C=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/download/forge/ForgeInstall.java | 92 ------------------- .../download/forge/ForgeInstallManifest.java | 32 +++++++ .../download/forge/ForgeInstallProfile.java | 4 +- .../hmcl/download/forge/ForgeInstallTask.java | 18 ++-- .../download/forge/ForgeInstallerType.java | 4 +- .../download/forge/ForgeOldInstallTask.java | 10 +- .../HMCLLegacyForgeHelper/build.gradle.kts | 2 +- ...Helper.java => HMCLLegacyForgeHelper.java} | 13 ++- 8 files changed, 55 insertions(+), 120 deletions(-) delete mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstall.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallManifest.java rename minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/{LegacyForgeHelper.java => HMCLLegacyForgeHelper.java} (92%) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstall.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstall.java deleted file mode 100644 index a7a40607998..00000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstall.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui 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 . - */ -package org.jackhuang.hmcl.download.forge; - -import org.jackhuang.hmcl.game.Artifact; -import org.jackhuang.hmcl.util.Immutable; - -/** - * - * @author huangyuhui - */ -@Immutable -public final class ForgeInstall { - - private final String profileName; - private final String target; - private final Artifact path; - private final String version; - private final String filePath; - private final String welcome; - private final String minecraft; - private final String mirrorList; - private final String logo; - - public ForgeInstall() { - this(null, null, null, null, null, null, null, null, null); - } - - public ForgeInstall(String profileName, String target, Artifact path, String version, String filePath, String welcome, String minecraft, String mirrorList, String logo) { - this.profileName = profileName; - this.target = target; - this.path = path; - this.version = version; - this.filePath = filePath; - this.welcome = welcome; - this.minecraft = minecraft; - this.mirrorList = mirrorList; - this.logo = logo; - } - - public String getProfileName() { - return profileName; - } - - public String getTarget() { - return target; - } - - public Artifact getPath() { - return path; - } - - public String getVersion() { - return version; - } - - public String getFilePath() { - return filePath; - } - - public String getWelcome() { - return welcome; - } - - public String getMinecraft() { - return minecraft; - } - - public String getMirrorList() { - return mirrorList; - } - - public String getLogo() { - return logo; - } - -} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallManifest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallManifest.java new file mode 100644 index 00000000000..c89f231f064 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallManifest.java @@ -0,0 +1,32 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui 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 . + */ +package org.jackhuang.hmcl.download.forge; + +import org.jackhuang.hmcl.game.Artifact; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.gson.JsonSerializable; + +/** + * + * @author huangyuhui + */ +@Immutable +@JsonSerializable +public record ForgeInstallManifest(String profileName, String target, Artifact path, String version, String filePath, + String welcome, String minecraft, String mirrorList, String logo) { +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallProfile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallProfile.java index 25482181990..fb3f97010b6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallProfile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallProfile.java @@ -27,7 +27,7 @@ /// @author huangyuhui @NotNullByDefault @JsonSerializable -public record ForgeInstallProfile(@SerializedName("install") ForgeInstall install, +public record ForgeInstallProfile(@SerializedName("install") ForgeInstallManifest install, @SerializedName("versionInfo") GameInstanceManifest versionInfo) { public ForgeInstallProfile { @@ -36,7 +36,7 @@ public record ForgeInstallProfile(@SerializedName("install") ForgeInstall instal } @Override - public ForgeInstall install() { + public ForgeInstallManifest install() { return install; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java index 6ad220ae6d6..97741bc2fc6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -117,10 +117,10 @@ public void execute() throws IOException, VersionMismatchException, UnsupportedI case LEGACY: dependency = new ForgeLegacyInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); break; - case NORMAL: + case OLD: dependency = new ForgeOldInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); break; - case MODERN: + case NEW: dependency = new GameDownloadTask(dependencyManager, manifest) .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( dependencyManager, @@ -152,12 +152,12 @@ public static ForgeInstallerType detectForgeInstallerType(String gameVersion, Pa ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); if (!gameVersion.equals(profile.getMinecraft())) throw new VersionMismatchException(profile.getMinecraft(), gameVersion); - return ForgeInstallerType.MODERN; + return ForgeInstallerType.NEW; } else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) { ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class); - if (!gameVersion.equals(profile.install().getMinecraft())) - throw new VersionMismatchException(profile.install().getMinecraft(), gameVersion); - return ForgeInstallerType.NORMAL; + if (!gameVersion.equals(profile.install().minecraft())) + throw new VersionMismatchException(profile.install().minecraft(), gameVersion); + return ForgeInstallerType.OLD; } else { throw new IOException(); } @@ -201,9 +201,9 @@ public static Task install( } else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) { checkCleanroomCompatibility(dependencyManager, manifest, gameVersion); ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class); - if (!gameVersion.equals(profile.install().getMinecraft())) - throw new VersionMismatchException(profile.install().getMinecraft(), gameVersion); - return new ForgeOldInstallTask(dependencyManager, manifest, modifyVersion(gameVersion, profile.install().getPath().getVersion().replaceAll("(?i)forge", "")), installer); + if (!gameVersion.equals(profile.install().minecraft())) + throw new VersionMismatchException(profile.install().minecraft(), gameVersion); + return new ForgeOldInstallTask(dependencyManager, manifest, modifyVersion(gameVersion, profile.install().path().getVersion().replaceAll("(?i)forge", "")), installer); } else { throw new IOException(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java index 7783ad832e7..fbab48bd0c0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java @@ -19,6 +19,6 @@ public enum ForgeInstallerType { LEGACY, - NORMAL, - MODERN + OLD, + NEW } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java index 6084d457e81..915f0d96cc6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java @@ -19,11 +19,7 @@ import org.jackhuang.hmcl.download.ArtifactMalformedException; import org.jackhuang.hmcl.download.DefaultDependencyManager; -import org.jackhuang.hmcl.game.GameComponentType; -import org.jackhuang.hmcl.game.GameInstanceManifest; -import org.jackhuang.hmcl.game.GameInstancePatch; -import org.jackhuang.hmcl.game.GameRepository; -import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.gson.JsonUtils; @@ -74,12 +70,12 @@ public void execute() throws Exception { ForgeInstallProfile installProfile = JsonUtils.fromNonNullJsonFully(stream, ForgeInstallProfile.class); // unpack the universal jar in the installer file. - Library forgeLibrary = new Library(installProfile.install().getPath()); + Library forgeLibrary = new Library(installProfile.install().path()); GameRepository gameRepository = dependencyManager.getGameRepository(); Path forgeFile = gameRepository.getLayout().getLibraryFile(manifest.id(), forgeLibrary); Files.createDirectories(forgeFile.getParent()); - ZipEntry forgeEntry = zipFile.getEntry(installProfile.install().getFilePath()); + ZipEntry forgeEntry = zipFile.getEntry(installProfile.install().filePath()); try (InputStream is = zipFile.getInputStream(forgeEntry); OutputStream os = Files.newOutputStream(forgeFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { is.transferTo(os); diff --git a/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts b/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts index 6f12a5a1b33..decaecb5afb 100644 --- a/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts +++ b/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts @@ -10,7 +10,7 @@ tasks.jar { attributes( "Created-By" to "Copyright(c) 2026 huangyuhui.", "Implementation-Version" to project.version, - "Premain-Class" to "org.jackhuang.hmcl.LegacyForgeHelper", + "Premain-Class" to "org.jackhuang.hmcl.HMCLLegacyForgeHelper", "Can-Redefine-Classes" to true, "Can-Retransform-Classes" to true ) diff --git a/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java b/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java similarity index 92% rename from minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java rename to minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java index 3cffab833a2..8b51ec04b30 100644 --- a/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/LegacyForgeHelper.java +++ b/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java @@ -23,20 +23,20 @@ import java.security.ProtectionDomain; @SuppressWarnings("JavaPrintToLogpoint") -public final class LegacyForgeHelper { - private LegacyForgeHelper () { +public final class HMCLLegacyForgeHelper { + private HMCLLegacyForgeHelper() { throw new AssertionError(); } private static final String TARGET_URL = "http://files.minecraftforge.net/fmllibs/%s"; - private static String newRootUrl = "https://files.multimc.org/fmllibs/%s"; + private static String newRootUrl = "https://https://hmcl.glavo.site/metadata/fmllibs/%s"; public static void premain(String agentArgs, Instrumentation inst) { if (agentArgs != null && !agentArgs.trim().isEmpty()) { newRootUrl = agentArgs.trim(); } - System.out.println("[LegacyForgeHelper] Loaded. Target RootURL will be replaced with: " + newRootUrl); + inst.addTransformer(new CoreFMLLibrariesTransformer()); } @@ -45,7 +45,6 @@ public static void agentmain(String agentArgs, Instrumentation inst) { } private final static class CoreFMLLibrariesTransformer implements ClassFileTransformer { - private static final String TARGET_CLASS = "cpw/mods/fml/relauncher/CoreFMLLibraries"; @Override public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { @@ -53,14 +52,14 @@ public byte[] transform(ClassLoader loader, String className, Class classBein return null; } - if (TARGET_CLASS.equals(className) || "cpw.mods.fml.relauncher.CoreFMLLibraries".equals(className)) { + if ("cpw/mods/fml/relauncher/CoreFMLLibraries".equals(className) || "cpw.mods.fml.relauncher.CoreFMLLibraries".equals(className)) { try { System.out.println("[LegacyForgeHelper] Transforming " + className + " ..."); byte[] modified = patchConstantPoolUtf8(classfileBuffer, TARGET_URL, newRootUrl); System.out.println("[LegacyForgeHelper] Successfully patched RootURL in " + className); return modified; } catch (Throwable t) { - System.err.println("[LegacyForgeHelper] Failed to patch class: " + t.getMessage()); + System.err.println("[LegacyForgeHelper] Failed to patch class"); t.printStackTrace(); } } From e40696e7d9216b415ae5fc958ec11122c6666b6e Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Thu, 27 Aug 2026 22:16:23 +0800 Subject: [PATCH 06/34] =?UTF-8?q?=E6=96=AD=E5=BC=A6=E5=90=AB=E6=B3=AA?= =?UTF-8?q?=E6=9B=B4=E5=BC=A0=20=E6=9C=80=E9=AB=98=E8=B4=B5=E6=B5=81?= =?UTF-8?q?=E6=B5=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../download/forge/ForgeBMCLVersionList.java | 102 +++--------------- .../download/forge/ForgeNewInstallTask.java | 10 +- .../hmcl/download/forge/ForgeVersion.java | 58 +--------- .../hmcl/download/forge/ForgeVersionList.java | 20 ++-- .../hmcl/download/forge/ForgeVersionRoot.java | 72 +------------ 5 files changed, 37 insertions(+), 225 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java index 33baccc25f2..6aae6fa7902 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java @@ -26,8 +26,6 @@ import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.Validation; import org.jackhuang.hmcl.util.io.NetworkUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.time.Instant; import java.time.format.DateTimeParseException; @@ -90,21 +88,21 @@ public Task refreshAsync(String gameVersion) { if (version == null) continue; List urls = new ArrayList<>(); - for (ForgeVersion.File file : version.getFiles()) - if (("installer".equals(file.getCategory()) && "jar".equals(file.getFormat())) || (("client".equals(file.getCategory()) || "universal".equals(file.getCategory())) && "zip".equals(file.getFormat()))) { - String branch = toLookupBranch(gameVersion, version.getBranch()); + for (ForgeVersion.File file : version.files()) + if (("installer".equals(file.category()) && "jar".equals(file.format())) || (("client".equals(file.category()) || "universal".equals(file.category())) && "zip".equals(file.format()))) { + String branch = toLookupBranch(gameVersion, version.branch()); - String classifier = lookupVersion + "-" + version.getVersion() + (branch.isEmpty() ? "" : '-' + branch); - String fileName1 = "forge-" + classifier + "-" + file.getCategory() + "." + file.getFormat(); - String fileName2 = "forge-" + classifier + "-" + lookupVersion + "-" + file.getCategory() + "." + file.getFormat(); + String classifier = lookupVersion + "-" + version.version() + (branch.isEmpty() ? "" : '-' + branch); + String fileName1 = "forge-" + classifier + "-" + file.category() + "." + file.format(); + String fileName2 = "forge-" + classifier + "-" + lookupVersion + "-" + file.category() + "." + file.format(); urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "/" + fileName1); urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "-" + lookupVersion + "/" + fileName2); urls.add(NetworkUtils.withQuery("https://bmclapi2.bangbang93.com/forge/download", mapOf( - pair("mcversion", version.getGameVersion()), - pair("version", version.getVersion()), + pair("mcversion", version.mcversion()), + pair("version", version.version()), pair("branch", branch), - pair("category", file.getCategory()), - pair("format", file.getFormat()) + pair("category", file.category()), + pair("format", file.format()) ))); } @@ -112,16 +110,16 @@ public Task refreshAsync(String gameVersion) { continue; Instant releaseDate = null; - if (version.getModified() != null) { + if (version.modified() != null) { try { - releaseDate = Instant.parse(version.getModified()); + releaseDate = Instant.parse(version.modified()); } catch (DateTimeParseException e) { - LOG.warning("Failed to parse instant " + version.getModified(), e); + LOG.warning("Failed to parse instant " + version.modified(), e); } } versions.put(gameVersion, new ForgeRemoteVersion( - fromLookupVersion(version.getGameVersion()), version.getVersion(), releaseDate, urls)); + fromLookupVersion(version.mcversion()), version.version(), releaseDate, urls)); } } finally { lock.writeLock().unlock(); @@ -136,14 +134,8 @@ public Optional getVersion(String gameVersion, String remote } @Immutable - public static final class ForgeVersion implements Validation { - - private final String branch; - private final int build; - private final String mcversion; - private final String modified; - private final String version; - private final List files; + public record ForgeVersion(String branch, int build, String mcversion, String modified, String version, + List files) implements Validation { /** * No-arg constructor for Gson. @@ -153,44 +145,6 @@ public ForgeVersion() { this(null, 0, "", null, "", Collections.emptyList()); } - public ForgeVersion(String branch, int build, String mcversion, String modified, String version, List files) { - this.branch = branch; - this.build = build; - this.mcversion = mcversion; - this.modified = modified; - this.version = version; - this.files = files; - } - - @Nullable - public String getBranch() { - return branch; - } - - public int getBuild() { - return build; - } - - @NotNull - public String getGameVersion() { - return mcversion; - } - - @Nullable - public String getModified() { - return modified; - } - - @NotNull - public String getVersion() { - return version; - } - - @NotNull - public List getFiles() { - return files; - } - @Override public void validate() throws JsonParseException { if (files == null) @@ -202,32 +156,10 @@ public void validate() throws JsonParseException { } @Immutable - public static final class File { - private final String format; - private final String category; - private final String hash; - + public record File(String format, String category, String hash) { public File() { this("", "", ""); } - - public File(String format, String category, String hash) { - this.format = format; - this.category = category; - this.hash = hash; - } - - public String getFormat() { - return format; - } - - public String getCategory() { - return category; - } - - public String getHash() { - return hash; - } } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java index 2d5d60b41fe..bbbf5c136c9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java @@ -20,9 +20,10 @@ import org.jackhuang.hmcl.download.ArtifactMalformedException; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile.Processor; -import org.jackhuang.hmcl.download.game.GameLibrariesTask; import org.jackhuang.hmcl.download.game.GameInstanceJsonDownloadTask; +import org.jackhuang.hmcl.download.game.GameLibrariesTask; import org.jackhuang.hmcl.game.*; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.DigestUtils; @@ -34,7 +35,6 @@ import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.CommandBuilder; -import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.SystemUtils; import org.jetbrains.annotations.NotNull; @@ -53,15 +53,15 @@ import java.util.jar.JarFile; import java.util.zip.ZipException; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.gson.JsonUtils.fromNonNullJson; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class ForgeNewInstallTask extends Task { private class ProcessorTask extends Task { - private Processor processor; - private Map vars; + private final Processor processor; + private final Map vars; public ProcessorTask(@NotNull Processor processor, @NotNull Map vars) { this.processor = processor; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java index a7bb771935f..f037f049da3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java @@ -26,61 +26,8 @@ * @author huangyuhui */ @Immutable -public final class ForgeVersion implements Validation { - - private final String branch; - private final String mcversion; - private final String jobver; - private final String version; - private final int build; - private final long modified; - private final String[][] files; - - /** - * No-arg constructor for Gson. - */ - @SuppressWarnings("unused") - public ForgeVersion() { - this(null, null, null, null, 0, 0, null); - } - - public ForgeVersion(String branch, String mcversion, String jobver, String version, int build, long modified, String[][] files) { - this.branch = branch; - this.mcversion = mcversion; - this.jobver = jobver; - this.version = version; - this.build = build; - this.modified = modified; - this.files = files; - } - - public String getBranch() { - return branch; - } - - public String getGameVersion() { - return mcversion; - } - - public String getJobver() { - return jobver; - } - - public String getVersion() { - return version; - } - - public int getBuild() { - return build; - } - - public long getModified() { - return modified; - } - - public String[][] getFiles() { - return files; - } +public record ForgeVersion(String branch, String mcversion, String jobver, String version, int build, long modified, + String[][] files) implements Validation { @Override public void validate() throws JsonParseException { @@ -91,5 +38,4 @@ public void validate() throws JsonParseException { if (mcversion == null) throw new JsonParseException("ForgeVersion mcversion cannot be null"); } - } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java index 9d0941b2b2b..c9f49822fe3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java @@ -64,28 +64,28 @@ public Task refreshAsync() { return; versions.clear(); - for (Map.Entry entry : root.getGameVersions().entrySet()) { + for (Map.Entry entry : root.mcversion().entrySet()) { String gameVersion = fromLookupVersion(VersionNumber.normalize(entry.getKey())); for (int v : entry.getValue()) { - ForgeVersion version = root.getNumber().get(v); + ForgeVersion version = root.number().get(v); if (version == null) continue; String jar = null; - for (String[] file : version.getFiles()) + for (String[] file : version.files()) if (file.length > 1 && "installer".equals(file[1])) { - String classifier = version.getGameVersion() + "-" + version.getVersion() - + (StringUtils.isNotBlank(version.getBranch()) ? "-" + version.getBranch() : ""); - String fileName = root.getArtifact() + "-" + classifier + "-" + file[1] + "." + file[0]; - jar = root.getWebPath() + classifier + "/" + fileName; + String classifier = version.mcversion() + "-" + version.version() + + (StringUtils.isNotBlank(version.branch()) ? "-" + version.branch() : ""); + String fileName = root.artifact() + "-" + classifier + "-" + file[1] + "." + file[0]; + jar = root.webpath() + classifier + "/" + fileName; } if (jar == null) continue; versions.put(gameVersion, new ForgeRemoteVersion( - toLookupVersion(version.getGameVersion()), - version.getVersion(), - version.getModified() > 0 ? Instant.ofEpochSecond(version.getModified()) : null, + toLookupVersion(version.mcversion()), + version.version(), + version.modified() > 0 ? Instant.ofEpochSecond(version.modified()) : null, Collections.singletonList(jar) )); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionRoot.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionRoot.java index cad542569b9..cf294625bb1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionRoot.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionRoot.java @@ -28,74 +28,9 @@ * @author huangyuhui */ @Immutable -public final class ForgeVersionRoot implements Validation { - - private final String artifact; - private final String webpath; - private final String adfly; - private final String homepage; - private final String name; - private final Map branches; - private final Map mcversion; - private final Map promos; - private final Map number; - - /** - * No-arg constructor for Gson. - */ - @SuppressWarnings("unused") - public ForgeVersionRoot() { - this(null, null, null, null, null, null, null, null, null); - } - - public ForgeVersionRoot(String artifact, String webpath, String adfly, String homepage, String name, Map branches, Map mcversion, Map promos, Map number) { - this.artifact = artifact; - this.webpath = webpath; - this.adfly = adfly; - this.homepage = homepage; - this.name = name; - this.branches = branches; - this.mcversion = mcversion; - this.promos = promos; - this.number = number; - } - - public String getArtifact() { - return artifact; - } - - public String getWebPath() { - return webpath; - } - - public String getAdfly() { - return adfly; - } - - public String getHomePage() { - return homepage; - } - - public String getName() { - return name; - } - - public Map getBranches() { - return branches; - } - - public Map getGameVersions() { - return mcversion; - } - - public Map getPromos() { - return promos; - } - - public Map getNumber() { - return number; - } - +public record ForgeVersionRoot(String artifact, String webpath, String adfly, String homepage, String name, + Map branches, Map mcversion, Map promos, + Map number) implements Validation { @Override public void validate() throws JsonParseException { if (number == null) @@ -103,5 +38,4 @@ public void validate() throws JsonParseException { if (mcversion == null) throw new JsonParseException("ForgeVersionRoot mcversion cannot be null"); } - } From 295b9eca2ab848bacf0f4b0861e757dc075cda7a Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Thu, 27 Aug 2026 23:08:18 +0800 Subject: [PATCH 07/34] =?UTF-8?q?=E9=9A=94=E5=87=A0=E4=B8=96=E7=8E=89?= =?UTF-8?q?=E6=A5=BC=E8=AF=95=E6=89=8D=E6=AD=8C=E5=92=8F=E7=90=B3=E7=90=85?= =?UTF-8?q?=20=E5=86=A0=E7=BB=9D=E4=BA=86=E4=B9=8C=E8=A1=A3=E5=B7=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/download/forge/ForgeInstallTask.java | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java index 97741bc2fc6..29ff4dc7475 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.download.forge; +import org.jackhuang.hmcl.addon.meta.ForgeOldModMetadata; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.UnsupportedInstallationException; import org.jackhuang.hmcl.download.VersionMismatchException; @@ -30,19 +31,19 @@ import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; +import java.util.*; import static org.jackhuang.hmcl.download.UnsupportedInstallationException.CLEANROOM_NOT_COMPATIBLE_WITH_FORGE; import static org.jackhuang.hmcl.download.UnsupportedInstallationException.UNSUPPORTED_LAUNCH_WRAPPER; import static org.jackhuang.hmcl.util.StringUtils.removePrefix; import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; /** * @@ -184,6 +185,10 @@ public static Task install( String gameVersion, Path installer) throws IOException, VersionMismatchException, UnsupportedInstallationException { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { + if ((Files.isRegularFile(fs.getPath("mod_MinecraftForge.class")) || Files.isRegularFile(fs.getPath("fmlversion.properties"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { + return new ForgeLegacyInstallTask(dependencyManager, manifest, tryGetLegacyForgeVersion(installer), installer); + } + String installProfileText = Files.readString(fs.getPath("install_profile.json")); Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); if (installProfile.containsKey("spec")) { @@ -231,4 +236,30 @@ private static void checkCleanroomCompatibility( private static String modifyVersion(String gameVersion, String version) { return removePrefix(removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_"), "forge-"); } + + public static @Nullable String tryGetLegacyForgeVersion(Path path) { + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(path)) { + if (Files.isRegularFile(fs.getPath("mod_MinecraftForge.info"))) { + ForgeOldModMetadata metadata = JsonUtils.fromNonNullJson(Files.readString(fs.getPath("mod_MinecraftForge.info")), ForgeOldModMetadata.class); + return metadata.getVersion(); + } + + if (Files.isRegularFile(fs.getPath("forgeversion.properties"))) { + Properties properties = new Properties(); + properties.load(Files.newInputStream(fs.getPath("forgeversion.properties"))); + List list = new ArrayList<>(); + list.add(properties.getProperty("forge.major.number")); + list.add(properties.getProperty("forge.minor.number")); + list.add(properties.getProperty("forge.revision.number")); + list.add(properties.getProperty("forge.build.number")); + return String.join(".", list); + } + + return null; + + } catch (IOException e) { + LOG.warning("Failed to get legacy forge version", e); + return null; + } + } } From 130d75584ec5430c512c02357b2ce76435a2212c Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Thu, 27 Aug 2026 23:33:25 +0800 Subject: [PATCH 08/34] =?UTF-8?q?=E8=B0=81=E4=B8=80=E8=AF=AD=E9=A3=8E?= =?UTF-8?q?=E7=B5=AE=E8=8D=A1=E8=8D=A1=20=E6=91=B9=E5=B0=BD=E8=90=BD?= =?UTF-8?q?=E9=9B=AA=E8=8C=AB=E8=8C=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + .../jackhuang/hmcl/game/HMCLGameLauncher.java | 42 ++++++ HMCLCore/build.gradle.kts | 1 + .../HMCLModloaderHelper/build.gradle.kts | 18 +++ .../jackhuang/hmcl/HMCLModloaderHelper.java | 142 ++++++++++++++++++ settings.gradle.kts | 3 +- 6 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 minecraft/libraries/HMCLModloaderHelper/build.gradle.kts create mode 100644 minecraft/libraries/HMCLModloaderHelper/src/main/java/org/jackhuang/hmcl/HMCLModloaderHelper.java diff --git a/.gitignore b/.gitignore index b4550b92488..479a726cb3a 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ language-subtag-registry /minecraft/libraries/HMCLTransformerDiscoveryService/out/ /minecraft/libraries/HMCLMultiMCBootstrap/out/ /minecraft/libraries/HMCLLegacyForgeHelper/out/ +/minecraft/libraries/HMCLModloaderHelper/out/ # eclipse /bin/ @@ -52,6 +53,7 @@ language-subtag-registry /HMCLCore/bin/ /minecraft/libraries/HMCLTransformerDiscoveryService/bin/ /minecraft/libraries/HMCLMultiMCBootstrap/bin/ +/minecraft/libraries/HMCLModloaderHelper/bin/ .classpath .project .settings diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java index 2bbfe1e75ee..9dea405f7ca 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java @@ -181,6 +181,15 @@ protected void appendJvmArgs(CommandBuilder result) { } } + if (this.instance.getVersion().compareTo("1.2") < 0 && this.instance.hasComponent(GameComponentType.FORGE)) { + LOG.info("Attempting to patch game with modloader-helper"); + try { + result.add("-javaagent:" + extractModloaderHelper()); + } catch (Exception e) { + LOG.warning("Failed to extract modloader-helper", e); + } + } + if (!options.isAllowAutoAgent() && !options.isNoGeneratedJVMArgs()) return; if (!options.isNoGeneratedOptimizingJVMArgs() @@ -264,4 +273,37 @@ private Path extractLegacyForgeHelper() throws IOException { FileUtils.saveSafely(agentPath, output -> output.write(bytes)); return agentPath; } + + private Path extractModloaderHelper() throws IOException { + Library library = new Library(new Artifact("org.jackhuang.hmcl", "modloader-helper", "1.0")); + String fileName = "HMCLModloaderHelper-1.0.jar"; + + Path agentPath = instance.getLayout().getLibraryFile(instance.getId(), library).toAbsolutePath().normalize(); + if (agentPath.toString().contains("=")) { + throw new IOException("Invalid library path: " + agentPath); + } + + byte[] bytes; + try (InputStream input = DefaultLauncher.class.getResourceAsStream("/assets/game/" + fileName)) { + if (input == null) { + throw new IOException("/assets/game/" + fileName + " not found"); + } + + bytes = input.readAllBytes(); + } + + if (Files.isRegularFile(agentPath)) { + try { + if (Files.size(agentPath) == bytes.length) { + return agentPath; + } + } catch (IOException e) { + LOG.warning("Failed to check size of " + agentPath, e); + } + } + + Files.createDirectories(agentPath.getParent()); + FileUtils.saveSafely(agentPath, output -> output.write(bytes)); + return agentPath; + } } diff --git a/HMCLCore/build.gradle.kts b/HMCLCore/build.gradle.kts index 7de04b35418..43c067aa88e 100644 --- a/HMCLCore/build.gradle.kts +++ b/HMCLCore/build.gradle.kts @@ -45,6 +45,7 @@ tasks.processResources { listOf( "HMCLTransformerDiscoveryService", "HMCLMultiMCBootstrap", + "HMCLModloaderHelper", "HMCLLegacyForgeHelper" ).map { project(":$it").tasks["jar"] as Jar }.forEach { task -> dependsOn(task) diff --git a/minecraft/libraries/HMCLModloaderHelper/build.gradle.kts b/minecraft/libraries/HMCLModloaderHelper/build.gradle.kts new file mode 100644 index 00000000000..b4d021d2763 --- /dev/null +++ b/minecraft/libraries/HMCLModloaderHelper/build.gradle.kts @@ -0,0 +1,18 @@ +version = "1.0" + +tasks.compileJava { + sourceCompatibility = "1.8" + targetCompatibility = "1.8" +} + +tasks.jar { + manifest { + attributes( + "Created-By" to "Copyright(c) 2026 huangyuhui.", + "Implementation-Version" to project.version, + "Premain-Class" to "org.jackhuang.hmcl.HMCLModloaderHelper", + "Can-Redefine-Classes" to true, + "Can-Retransform-Classes" to true + ) + } +} diff --git a/minecraft/libraries/HMCLModloaderHelper/src/main/java/org/jackhuang/hmcl/HMCLModloaderHelper.java b/minecraft/libraries/HMCLModloaderHelper/src/main/java/org/jackhuang/hmcl/HMCLModloaderHelper.java new file mode 100644 index 00000000000..e0087ff5ca0 --- /dev/null +++ b/minecraft/libraries/HMCLModloaderHelper/src/main/java/org/jackhuang/hmcl/HMCLModloaderHelper.java @@ -0,0 +1,142 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui 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 . + */ +package org.jackhuang.hmcl; + +import java.io.File; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.lang.reflect.Field; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.security.CodeSource; +import java.security.ProtectionDomain; + +public final class HMCLModloaderHelper { + private HMCLModloaderHelper() { + throw new AssertionError(); + } + + public static void premain(String agentArgs, Instrumentation inst) { + initAgent(inst); + } + + public static void agentmain(String agentArgs, Instrumentation inst) { + initAgent(inst); + } + + private static void initAgent(Instrumentation inst) { + for (Class loadedClass : inst.getAllLoadedClasses()) { + try { + fixProtectionDomain(loadedClass.getProtectionDomain()); + } catch (Throwable ignored) { + } + } + + inst.addTransformer(new ClassFileTransformer() { + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { + if (protectionDomain != null) { + fixProtectionDomain(protectionDomain); + } + return null; + } + }, true); + } + + private static void fixProtectionDomain(ProtectionDomain pd) { + if (pd == null) return; + try { + CodeSource cs = pd.getCodeSource(); + if (cs != null) { + fixCodeSource(cs); + } + } catch (Throwable ignored) { + } + } + + private static void fixCodeSource(CodeSource cs) { + try { + URL location = cs.getLocation(); + if (location == null) return; + + URL fixedLocation = cleanUrl(location); + if (fixedLocation != null && !fixedLocation.equals(location)) { + Field locationField = CodeSource.class.getDeclaredField("location"); + locationField.setAccessible(true); + locationField.set(cs, fixedLocation); + } + } catch (Throwable t) { + t.printStackTrace(); + } + } + + private static URL cleanUrl(URL url) { + if (url == null) return null; + String urlString = url.toString(); + + if (urlString.startsWith("jar:") || "jar".equalsIgnoreCase(url.getProtocol())) { + String inner = urlString.substring(4); + int bangIndex = inner.indexOf('!'); + if (bangIndex != -1) { + inner = inner.substring(0, bangIndex); + } + try { + return new URL(inner); + } catch (MalformedURLException e) { + if (inner.startsWith("file:")) { + inner = inner.substring(5); + } + try { + return new File(inner).toURI().toURL(); + } catch (MalformedURLException ignored) { + } + } + } + + try { + URI uri = url.toURI(); + if (uri.isOpaque() || (uri.getScheme() != null && !uri.getScheme().equalsIgnoreCase("file"))) { + String path = url.getPath(); + if (path != null) { + if (path.startsWith("file:")) { + path = path.substring(5); + } + int bangIndex = path.indexOf('!'); + if (bangIndex != -1) { + path = path.substring(0, bangIndex); + } + return new File(path).toURI().toURL(); + } + } + } catch (Exception e) { + try { + String path = url.getPath(); + if (path != null) { + int bangIndex = path.indexOf('!'); + if (bangIndex != -1) { + path = path.substring(0, bangIndex); + } + return new File(path).toURI().toURL(); + } + } catch (Exception ignored) { + } + } + return url; + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index c35fe1bc1d9..475625a8e4d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,7 +5,8 @@ include( "HMCLBoot" ) -val minecraftLibraries = listOf("HMCLTransformerDiscoveryService", "HMCLMultiMCBootstrap", "HMCLLegacyForgeHelper") +val minecraftLibraries = + listOf("HMCLTransformerDiscoveryService", "HMCLMultiMCBootstrap", "HMCLLegacyForgeHelper", "HMCLModloaderHelper") include(minecraftLibraries) for (library in minecraftLibraries) { From 3e1bbddd978e73a6d7cd2077485c8f6d185e7366 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 07:32:04 +0800 Subject: [PATCH 09/34] =?UTF-8?q?=E8=B0=81=E4=B8=87=E9=87=8C=E5=85=B3?= =?UTF-8?q?=E5=B1=B1=E6=9A=82=E5=BF=98=E5=A5=B3=E5=84=BF=E6=A8=A1=E6=A0=B7?= =?UTF-8?q?=20=E5=AE=B6=E4=B8=8E=E5=9B=BD=E9=83=BD=E6=8B=85=E5=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 479a726cb3a..c0214f06aab 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ language-subtag-registry /minecraft/libraries/HMCLTransformerDiscoveryService/build/ /minecraft/libraries/HMCLMultiMCBootstrap/build/ /minecraft/libraries/HMCLLegacyForgeHelper/build/ +/minecraft/libraries/HMCLModloaderHelper/build/ /buildSrc/build/ # idea @@ -53,6 +54,7 @@ language-subtag-registry /HMCLCore/bin/ /minecraft/libraries/HMCLTransformerDiscoveryService/bin/ /minecraft/libraries/HMCLMultiMCBootstrap/bin/ +/minecraft/libraries/HMCLLegacyForgeHelper/bin/ /minecraft/libraries/HMCLModloaderHelper/bin/ .classpath .project From b11cd876e7192095c9ca9d4574fbb26f30cc9fd4 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 07:37:27 +0800 Subject: [PATCH 10/34] =?UTF-8?q?=E5=86=8D=E4=BA=B2=E6=89=8B=E6=91=98?= =?UTF-8?q?=E4=B8=8B=E9=BB=84=E6=B2=B3=E8=90=BD=E6=97=A5=E9=BB=91=E5=B1=B1?= =?UTF-8?q?=E9=A3=9E=E9=9C=9C=20=E9=93=81=E7=94=B2=E6=8F=A9=E4=BA=AE=20?= =?UTF-8?q?=E8=88=8D=E6=97=A7=E6=97=B6=E4=BA=91=E8=A3=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/jackhuang/hmcl/game/LauncherHelper.java | 2 +- .../hmcl/download/game/GameVerificationFixTask.java | 7 +------ .../jackhuang/hmcl/game/DefaultGameInstanceTest.java | 10 ++-------- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 8ed284d6b93..a8f6fcbbaa3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -216,7 +216,7 @@ private void launch0() { }) ); }).withStage("launch.state.dependencies") - .thenComposeAsync(() -> new GameVerificationFixTask(gameInstance, gameInstance.getVersion(), launchManifest.get())) + .thenComposeAsync(() -> new GameVerificationFixTask(gameInstance, gameInstance.getVersion())) .thenComposeAsync(() -> { if (setting.getInheritable(GameSettings::allowAutoAgentProperty) || setting.getInheritable(GameSettings::noJVMOptionsProperty) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java index 510073482fb..5a7d4f2f1f5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java @@ -19,7 +19,6 @@ import org.jackhuang.hmcl.game.GameComponentType; import org.jackhuang.hmcl.game.GameInstance; -import org.jackhuang.hmcl.game.GameInstanceManifest; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; @@ -41,18 +40,14 @@ public final class GameVerificationFixTask extends Task { /// The detected Minecraft version. private final GameVersionNumber gameVersion; - /// The effective launch manifest used to detect Forge. - private final GameInstanceManifest manifest; - /// Creates a task for a fixed instance and effective launch manifest. /// /// @param instance the instance whose client jar may be modified /// @param gameVersion the detected Minecraft version /// @param manifest the effective launch manifest used to detect Forge - public GameVerificationFixTask(GameInstance instance, GameVersionNumber gameVersion, GameInstanceManifest manifest) { + public GameVerificationFixTask(GameInstance instance, GameVersionNumber gameVersion) { this.instance = instance; this.gameVersion = gameVersion; - this.manifest = manifest; setSignificance(TaskSignificance.MODERATE); } diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/DefaultGameInstanceTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/DefaultGameInstanceTest.java index ab9df59627f..73593e77968 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/DefaultGameInstanceTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/DefaultGameInstanceTest.java @@ -46,13 +46,7 @@ import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; /// Tests snapshot-bound behavior of [DefaultGameInstance]. @NotNullByDefault @@ -355,7 +349,7 @@ public void testVerificationFixKeepsCapturedInstance(@TempDir Path tempDirectory tempDirectory.resolve("versions/instance/current.json")); writeSignedJar(current.getInstanceJarFile()); - new GameVerificationFixTask(captured, GameVersionNumber.asGameVersion("1.5.2"), manifest).execute(); + new GameVerificationFixTask(captured, GameVersionNumber.asGameVersion("1.5.2")).execute(); assertFalse(hasZipEntry(captured.getInstanceJarFile(), "META-INF/MOJANG_C.DSA")); assertFalse(hasZipEntry(captured.getInstanceJarFile(), "META-INF/MOJANG_C.SF")); From a51cf512b652e33b2b0b2df73b54db9878dc2219 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 07:40:01 +0800 Subject: [PATCH 11/34] =?UTF-8?q?=E4=B9=B1=E4=B8=96=E4=B8=B9=E5=BF=83?= =?UTF-8?q?=E7=A2=A7=E8=A1=80=E6=BB=A1=E8=85=94=20=E7=81=B5=E6=80=9D?= =?UTF-8?q?=E5=8D=83=E8=BD=AC=E4=B8=80=E6=84=8F=E8=BE=85=E9=9A=8B=E6=9D=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java index 9dea405f7ca..3b2b5179765 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java @@ -172,6 +172,8 @@ public void makeLaunchScript(Path scriptFile) throws IOException { protected void appendJvmArgs(CommandBuilder result) { super.appendJvmArgs(result); + if (!options.isAllowAutoAgent() && !options.isNoGeneratedJVMArgs()) return; + if (this.instance.getVersion().compareTo("1.6") < 0 && this.instance.hasComponent(GameComponentType.FORGE)) { LOG.info("Attempting to patch game with legacy-forge-helper"); try { @@ -190,8 +192,6 @@ protected void appendJvmArgs(CommandBuilder result) { } } - if (!options.isAllowAutoAgent() && !options.isNoGeneratedJVMArgs()) return; - if (!options.isNoGeneratedOptimizingJVMArgs() && NativePatcher.needPatchMemoryUtil(manifest, options.getJava().getParsedVersion())) { LOG.info("Attempting to patch game with lwjgl-unsafe-agent"); From 45b23cc1a4da426839b1b2469899b774499e613a Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 07:46:41 +0800 Subject: [PATCH 12/34] =?UTF-8?q?=E6=B2=B3=E5=B1=B1=E7=BA=B7=E8=A3=82?= =?UTF-8?q?=E7=BB=88=E9=A1=BB=E5=85=A5=E6=8E=8C=20=E7=99=BE=E4=B8=87?= =?UTF-8?q?=E6=98=8E=E7=8F=A0=E6=8A=AC=E6=89=8B=E8=B5=90=E5=90=8D=E5=B0=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../download/DefaultDependencyManager.java | 4 +- .../cleanroom/CleanroomInstallTask.java | 6 +- .../hmcl/download/forge/ForgeInstallTask.java | 144 +------------- .../download/forge/ForgeInstallation.java | 184 ++++++++++++++++++ .../forge/ForgeNewInstallProfile.java | 91 ++++----- .../download/forge/ForgeNewInstallTask.java | 28 +-- .../neoforge/NeoForgeInstallTask.java | 19 +- .../neoforge/NeoForgeOldInstallTask.java | 34 ++-- 8 files changed, 272 insertions(+), 238 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java index b9b34c3b62f..a4631ed28b7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java @@ -18,7 +18,7 @@ package org.jackhuang.hmcl.download; import org.jackhuang.hmcl.download.cleanroom.CleanroomInstallTask; -import org.jackhuang.hmcl.download.forge.ForgeInstallTask; +import org.jackhuang.hmcl.download.forge.ForgeInstallation; import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; import org.jackhuang.hmcl.download.game.GameDownloadTask; import org.jackhuang.hmcl.download.game.GameLibrariesTask; @@ -350,7 +350,7 @@ public Task installComponentAsync( } try { - return ForgeInstallTask.install(this, baseManifest, gameVersion, installer); + return ForgeInstallation.install(this, baseManifest, gameVersion, installer); } catch (IOException ignore) { } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomInstallTask.java index c3050243757..04bdb2b51be 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomInstallTask.java @@ -188,13 +188,13 @@ public static Task install( } ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); - if (!gameVersion.equals(profile.getMinecraft())) - throw new VersionMismatchException(profile.getMinecraft(), gameVersion); + if (!gameVersion.equals(profile.minecraft())) + throw new VersionMismatchException(profile.minecraft(), gameVersion); return new CleanroomInstallTask( dependencyManager, manifest, gameVersion, - modifyVersion(profile.getVersion()), + modifyVersion(profile.version()), installer); } else { throw new IOException(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java index 29ff4dc7475..f9de720e4a9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -17,33 +17,25 @@ */ package org.jackhuang.hmcl.download.forge; -import org.jackhuang.hmcl.addon.meta.ForgeOldModMetadata; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.UnsupportedInstallationException; import org.jackhuang.hmcl.download.VersionMismatchException; import org.jackhuang.hmcl.download.game.GameDownloadTask; import org.jackhuang.hmcl.game.GameComponentAnalyzer; -import org.jackhuang.hmcl.game.GameComponentType; import org.jackhuang.hmcl.game.GameInstanceManifest; import org.jackhuang.hmcl.game.GameInstancePatch; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.util.gson.JsonUtils; -import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; -import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.Collection; +import java.util.Collections; -import static org.jackhuang.hmcl.download.UnsupportedInstallationException.CLEANROOM_NOT_COMPATIBLE_WITH_FORGE; import static org.jackhuang.hmcl.download.UnsupportedInstallationException.UNSUPPORTED_LAUNCH_WRAPPER; -import static org.jackhuang.hmcl.util.StringUtils.removePrefix; -import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; +import static org.jackhuang.hmcl.download.forge.ForgeInstallation.detectForgeInstallerType; /** * @@ -132,134 +124,4 @@ public void execute() throws IOException, VersionMismatchException, UnsupportedI break; } } - - /// Returns whether a Forge installer uses the processor-based format. - /// - /// @param gameVersion Minecraft version expected by the installation - /// @param installer the Forge installer JAR - /// @return `true` for the processor-based format, or `false` for the legacy format - /// @throws IOException if the installer profile is missing, malformed, or - /// unsupported - /// @throws VersionMismatchException if the installer targets another Minecraft version - public static ForgeInstallerType detectForgeInstallerType(String gameVersion, Path installer) throws IOException, VersionMismatchException { - try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { - if ((Files.isRegularFile(fs.getPath("mod_MinecraftForge.class")) || Files.isRegularFile(fs.getPath("fmlversion.properties"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { - return ForgeInstallerType.LEGACY; - } - - String installProfileText = Files.readString(fs.getPath("install_profile.json")); - Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); - if (installProfile.containsKey("spec")) { - ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); - if (!gameVersion.equals(profile.getMinecraft())) - throw new VersionMismatchException(profile.getMinecraft(), gameVersion); - return ForgeInstallerType.NEW; - } else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) { - ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class); - if (!gameVersion.equals(profile.install().minecraft())) - throw new VersionMismatchException(profile.install().minecraft(), gameVersion); - return ForgeInstallerType.OLD; - } else { - throw new IOException(); - } - } - } - - /// Creates a task that installs Forge from a local installer JAR. - /// - /// Processor-based installers obtain a verified vanilla client JAR from shared cache storage; - /// neither format reads an instance JAR. - /// - /// @param dependencyManager repository-scoped download services - /// @param manifest working manifest receiving the Forge patch - /// @param gameVersion Minecraft version expected by the installation - /// @param installer the Forge installer JAR - /// @return the task producing the Forge patch - /// @throws IOException if the installer profile is missing, malformed, or - /// unsupported - /// @throws VersionMismatchException if the installer targets another Minecraft version - /// @throws UnsupportedInstallationException if the manifest already contains Cleanroom - public static Task install( - DefaultDependencyManager dependencyManager, - GameInstanceManifest manifest, - String gameVersion, - Path installer) throws IOException, VersionMismatchException, UnsupportedInstallationException { - try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { - if ((Files.isRegularFile(fs.getPath("mod_MinecraftForge.class")) || Files.isRegularFile(fs.getPath("fmlversion.properties"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { - return new ForgeLegacyInstallTask(dependencyManager, manifest, tryGetLegacyForgeVersion(installer), installer); - } - - String installProfileText = Files.readString(fs.getPath("install_profile.json")); - Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); - if (installProfile.containsKey("spec")) { - checkCleanroomCompatibility(dependencyManager, manifest, gameVersion); - ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); - if (!gameVersion.equals(profile.getMinecraft())) - throw new VersionMismatchException(profile.getMinecraft(), gameVersion); - return new GameDownloadTask(dependencyManager, manifest) - .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( - dependencyManager, - manifest, - minecraftJar, - modifyVersion(gameVersion, profile.getVersion()), - installer)); - } else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) { - checkCleanroomCompatibility(dependencyManager, manifest, gameVersion); - ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class); - if (!gameVersion.equals(profile.install().minecraft())) - throw new VersionMismatchException(profile.install().minecraft(), gameVersion); - return new ForgeOldInstallTask(dependencyManager, manifest, modifyVersion(gameVersion, profile.install().path().getVersion().replaceAll("(?i)forge", "")), installer); - } else { - throw new IOException(); - } - } - } - - /// Rejects Forge installation when the manifest already contains Cleanroom. - /// - /// @param dependencyManager repository-scoped download services - /// @param manifest working manifest receiving the Forge patch - /// @param gameVersion Minecraft version used for component analysis - /// @throws UnsupportedInstallationException if the manifest already contains Cleanroom - private static void checkCleanroomCompatibility( - DefaultDependencyManager dependencyManager, - GameInstanceManifest manifest, - String gameVersion) throws UnsupportedInstallationException { - GameComponentAnalyzer analyzer = GameComponentAnalyzer.analyze( - dependencyManager.getGameRepository().resolve(manifest), - GameVersionNumber.asGameVersion(gameVersion)); - if (analyzer.has(GameComponentType.CLEANROOM)) { - throw new UnsupportedInstallationException(CLEANROOM_NOT_COMPATIBLE_WITH_FORGE); - } - } - - private static String modifyVersion(String gameVersion, String version) { - return removePrefix(removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_"), "forge-"); - } - - public static @Nullable String tryGetLegacyForgeVersion(Path path) { - try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(path)) { - if (Files.isRegularFile(fs.getPath("mod_MinecraftForge.info"))) { - ForgeOldModMetadata metadata = JsonUtils.fromNonNullJson(Files.readString(fs.getPath("mod_MinecraftForge.info")), ForgeOldModMetadata.class); - return metadata.getVersion(); - } - - if (Files.isRegularFile(fs.getPath("forgeversion.properties"))) { - Properties properties = new Properties(); - properties.load(Files.newInputStream(fs.getPath("forgeversion.properties"))); - List list = new ArrayList<>(); - list.add(properties.getProperty("forge.major.number")); - list.add(properties.getProperty("forge.minor.number")); - list.add(properties.getProperty("forge.revision.number")); - list.add(properties.getProperty("forge.build.number")); - return String.join(".", list); - } - - return null; - - } catch (IOException e) { - LOG.warning("Failed to get legacy forge version", e); - return null; - } - } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java new file mode 100644 index 00000000000..5ffbbeea585 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java @@ -0,0 +1,184 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui 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 . + */ +package org.jackhuang.hmcl.download.forge; + +import org.jackhuang.hmcl.addon.meta.ForgeOldModMetadata; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.UnsupportedInstallationException; +import org.jackhuang.hmcl.download.VersionMismatchException; +import org.jackhuang.hmcl.download.game.GameDownloadTask; +import org.jackhuang.hmcl.game.GameComponentAnalyzer; +import org.jackhuang.hmcl.game.GameComponentType; +import org.jackhuang.hmcl.game.GameInstanceManifest; +import org.jackhuang.hmcl.game.GameInstancePatch; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.CompressingUtils; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import static org.jackhuang.hmcl.download.UnsupportedInstallationException.CLEANROOM_NOT_COMPATIBLE_WITH_FORGE; +import static org.jackhuang.hmcl.util.StringUtils.removePrefix; +import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +public class ForgeInstallation { + private ForgeInstallation() { + throw new AssertionError(); + } + + + /// Returns whether a Forge installer uses the processor-based format. + /// + /// @param gameVersion Minecraft version expected by the installation + /// @param installer the Forge installer JAR + /// @return `true` for the processor-based format, or `false` for the legacy format + /// @throws IOException if the installer profile is missing, malformed, or + /// unsupported + /// @throws VersionMismatchException if the installer targets another Minecraft version + public static ForgeInstallerType detectForgeInstallerType(String gameVersion, Path installer) throws IOException, VersionMismatchException { + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { + if ((Files.isRegularFile(fs.getPath("mod_MinecraftForge.class")) || Files.isRegularFile(fs.getPath("fmlversion.properties"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { + return ForgeInstallerType.LEGACY; + } + + String installProfileText = Files.readString(fs.getPath("install_profile.json")); + Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); + if (installProfile.containsKey("spec")) { + ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); + if (!gameVersion.equals(profile.minecraft())) + throw new VersionMismatchException(profile.minecraft(), gameVersion); + return ForgeInstallerType.NEW; + } else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) { + ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class); + if (!gameVersion.equals(profile.install().minecraft())) + throw new VersionMismatchException(profile.install().minecraft(), gameVersion); + return ForgeInstallerType.OLD; + } else { + throw new IOException(); + } + } + } + + /// Creates a task that installs Forge from a local installer JAR. + /// + /// Processor-based installers obtain a verified vanilla client JAR from shared cache storage; + /// neither format reads an instance JAR. + /// + /// @param dependencyManager repository-scoped download services + /// @param manifest working manifest receiving the Forge patch + /// @param gameVersion Minecraft version expected by the installation + /// @param installer the Forge installer JAR + /// @return the task producing the Forge patch + /// @throws IOException if the installer profile is missing, malformed, or + /// unsupported + /// @throws VersionMismatchException if the installer targets another Minecraft version + /// @throws UnsupportedInstallationException if the manifest already contains Cleanroom + public static Task install( + DefaultDependencyManager dependencyManager, + GameInstanceManifest manifest, + String gameVersion, + Path installer) throws IOException, VersionMismatchException, UnsupportedInstallationException { + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { + if ((Files.isRegularFile(fs.getPath("mod_MinecraftForge.class")) || Files.isRegularFile(fs.getPath("fmlversion.properties"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { + return new ForgeLegacyInstallTask(dependencyManager, manifest, tryGetLegacyForgeVersion(installer), installer); + } + + String installProfileText = Files.readString(fs.getPath("install_profile.json")); + Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); + if (installProfile.containsKey("spec")) { + checkCleanroomCompatibility(dependencyManager, manifest, gameVersion); + ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); + if (!gameVersion.equals(profile.minecraft())) + throw new VersionMismatchException(profile.minecraft(), gameVersion); + return new GameDownloadTask(dependencyManager, manifest) + .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( + dependencyManager, + manifest, + minecraftJar, + modifyVersion(gameVersion, profile.version()), + installer)); + } else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) { + checkCleanroomCompatibility(dependencyManager, manifest, gameVersion); + ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class); + if (!gameVersion.equals(profile.install().minecraft())) + throw new VersionMismatchException(profile.install().minecraft(), gameVersion); + return new ForgeOldInstallTask(dependencyManager, manifest, modifyVersion(gameVersion, profile.install().path().getVersion().replaceAll("(?i)forge", "")), installer); + } else { + throw new IOException(); + } + } + } + + /// Rejects Forge installation when the manifest already contains Cleanroom. + /// + /// @param dependencyManager repository-scoped download services + /// @param manifest working manifest receiving the Forge patch + /// @param gameVersion Minecraft version used for component analysis + /// @throws UnsupportedInstallationException if the manifest already contains Cleanroom + private static void checkCleanroomCompatibility( + DefaultDependencyManager dependencyManager, + GameInstanceManifest manifest, + String gameVersion) throws UnsupportedInstallationException { + GameComponentAnalyzer analyzer = GameComponentAnalyzer.analyze( + dependencyManager.getGameRepository().resolve(manifest), + GameVersionNumber.asGameVersion(gameVersion)); + if (analyzer.has(GameComponentType.CLEANROOM)) { + throw new UnsupportedInstallationException(CLEANROOM_NOT_COMPATIBLE_WITH_FORGE); + } + } + + private static String modifyVersion(String gameVersion, String version) { + return removePrefix(removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_"), "forge-"); + } + + public static @Nullable String tryGetLegacyForgeVersion(Path path) { + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(path)) { + if (Files.isRegularFile(fs.getPath("mod_MinecraftForge.info"))) { + ForgeOldModMetadata metadata = JsonUtils.fromNonNullJson(Files.readString(fs.getPath("mod_MinecraftForge.info")), ForgeOldModMetadata.class); + return metadata.getVersion(); + } + + if (Files.isRegularFile(fs.getPath("forgeversion.properties"))) { + Properties properties = new Properties(); + properties.load(Files.newInputStream(fs.getPath("forgeversion.properties"))); + List list = new ArrayList<>(); + list.add(properties.getProperty("forge.major.number")); + list.add(properties.getProperty("forge.minor.number")); + list.add(properties.getProperty("forge.revision.number")); + list.add(properties.getProperty("forge.build.number")); + return String.join(".", list); + } + + return null; + + } catch (IOException e) { + LOG.warning("Failed to get legacy forge version", e); + return null; + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallProfile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallProfile.java index d388403cd68..85c01b3fd6e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallProfile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallProfile.java @@ -29,47 +29,33 @@ import java.util.stream.Collectors; @Immutable -public class ForgeNewInstallProfile implements Validation { - - private final int spec; - private final String minecraft; - private final String json; - private final String version; - private final Artifact path; - private final List libraries; - private final List processors; - private final Map data; - - public ForgeNewInstallProfile(int spec, String minecraft, String json, String version, Artifact path, List libraries, List processors, Map data) { - this.spec = spec; - this.minecraft = minecraft; - this.json = json; - this.version = version; - this.path = path; - this.libraries = libraries; - this.processors = processors; - this.data = data; - } +public record ForgeNewInstallProfile(int spec, String minecraft, String json, String version, Artifact path, + List libraries, List processors, + Map data) implements Validation { /** * Specification for install_profile.json. */ - public int getSpec() { + @Override + public int spec() { return spec; } /** * Vanilla game version that this installer supports. */ - public String getMinecraft() { + @Override + public String minecraft() { return minecraft; } /** * Version json to be installed. + * * @return path of the version json relative to the installer JAR file. */ - public String getJson() { + @Override + public String json() { return json; } @@ -77,12 +63,14 @@ public String getJson() { * * @return forge version. */ - public String getVersion() { + @Override + public String version() { return version; } /** * Maven artifact path for the main jar to install. + * * @return artifact path of the main jar. */ public Optional getPath() { @@ -91,16 +79,19 @@ public Optional getPath() { /** * Libraries that processors depend on. + * * @return the required dependencies. */ - public List getLibraries() { + @Override + public List libraries() { return libraries == null ? Collections.emptyList() : libraries; } /** * Tasks to be executed to setup modded environment. */ - public List getProcessors() { + @Override + public List processors() { if (processors == null) return Collections.emptyList(); return processors.stream().filter(p -> p.isSide("client")).collect(Collectors.toList()); } @@ -114,7 +105,7 @@ public Map getData() { if (data == null) return new HashMap<>(); - return data.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getClient())); + return data.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().client())); } @Override @@ -123,23 +114,12 @@ public void validate() throws JsonParseException, TolerableValidationException { throw new JsonParseException("ForgeNewInstallProfile is malformed"); } - public static class Processor implements Validation { - private final List sides; - private final Artifact jar; - private final List classpath; - private final List args; - private final Map outputs; - - public Processor(List sides, Artifact jar, List classpath, List args, Map outputs) { - this.sides = sides; - this.jar = jar; - this.classpath = classpath; - this.args = args; - this.outputs = outputs; - } + public record Processor(List sides, Artifact jar, List classpath, List args, + Map outputs) implements Validation { /** * Check which side this processor should be run on. We only support client install currently. + * * @param side can be one of "client", "server", "extract". * @return true if the processor can run on the side. */ @@ -149,17 +129,21 @@ public boolean isSide(String side) { /** * The executable jar of this processor task. Will be executed in installation process. + * * @return the artifact path of executable jar. */ - public Artifact getJar() { + @Override + public Artifact jar() { return jar; } /** * The dependencies of this processor task. + * * @return the artifact path of dependencies. */ - public List getClasspath() { + @Override + public List classpath() { return classpath == null ? Collections.emptyList() : classpath; } @@ -170,10 +154,12 @@ public List getClasspath() { * {entry}: Get corresponding value of the entry in {@link ForgeNewInstallProfile#getData()} * {MINECRAFT_JAR}: path of the Minecraft jar. * {SIDE}: values other than "client" will be ignored. + * * @return arguments to pass to the processor jar. * @see ForgeNewInstallTask#parseLiteral(String, Map, ExceptionalFunction) */ - public List getArgs() { + @Override + public List args() { return args == null ? Collections.emptyList() : args; } @@ -182,10 +168,12 @@ public List getArgs() { * Arguments to pass to the processor jar. * Keys can be in one of [artifact] or {entry}. Should be file path. * Values can be in one of {entry} or 'literal'. Should be SHA-1 checksum. + * * @return files output from this processor. * @see ForgeNewInstallTask#parseLiteral(String, Map, ExceptionalFunction) */ - public Map getOutputs() { + @Override + public Map outputs() { return outputs == null ? Collections.emptyMap() : outputs; } @@ -196,21 +184,18 @@ public void validate() throws JsonParseException, TolerableValidationException { } } - public static class Datum { - private final String client; - - public Datum(String client) { - this.client = client; - } + public record Datum(String client) { /** * Can be in the following formats: * [value]: An artifact path. * 'value': A string literal. * value: A file in the installer package, to be extracted to a temp folder, and then have the absolute path in replacements. + * * @return Value to use for the client install */ - public String getClient() { + @Override + public String client() { return client; } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java index bbbf5c136c9..b8e6fac7ff3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java @@ -74,7 +74,7 @@ public void execute() throws Exception { Map outputs = new HashMap<>(); boolean miss = false; - for (Map.Entry entry : processor.getOutputs().entrySet()) { + for (Map.Entry entry : processor.outputs().entrySet()) { String key = entry.getKey(); String value = entry.getValue(); @@ -105,11 +105,11 @@ public void execute() throws Exception { } } - if (!processor.getOutputs().isEmpty() && !miss) { + if (!processor.outputs().isEmpty() && !miss) { return; } - Path jar = gameRepository.getLayout().getArtifactFile(processor.getJar()); + Path jar = gameRepository.getLayout().getArtifactFile(processor.jar()); if (!Files.isRegularFile(jar)) throw new FileNotFoundException("Game processor file not found, should be downloaded in preprocess"); @@ -125,8 +125,8 @@ public void execute() throws Exception { command.add(JavaRuntime.getDefault().getBinary().toString()); command.add("-cp"); - List classpath = new ArrayList<>(processor.getClasspath().size() + 1); - for (Artifact artifact : processor.getClasspath()) { + List classpath = new ArrayList<>(processor.classpath().size() + 1); + for (Artifact artifact : processor.classpath()) { Path file = gameRepository.getLayout().getArtifactFile(artifact); if (!Files.isRegularFile(file)) throw new Exception("Game processor dependency missing"); @@ -137,8 +137,8 @@ public void execute() throws Exception { command.add(mainClass); - List args = new ArrayList<>(processor.getArgs().size()); - for (String arg : processor.getArgs()) { + List args = new ArrayList<>(processor.args().size()); + for (String arg : processor.args()) { String parsed = parseLiteral(arg, vars); if (parsed == null) throw new ArtifactMalformedException("Invalid forge installation configuration"); @@ -147,7 +147,7 @@ public void execute() throws Exception { command.addAll(args); - LOG.info("Executing external processor " + processor.getJar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString()); + LOG.info("Executing external processor " + processor.jar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString()); int exitCode = SystemUtils.callExternalProcess(command); if (exitCode != 0) throw new IOException("Game processor exited abnormally with code " + exitCode); @@ -304,10 +304,10 @@ public boolean doPreExecute() { public void preExecute() throws Exception { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { profile = JsonUtils.fromNonNullJson(Files.readString(fs.getPath("install_profile.json")), ForgeNewInstallProfile.class); - processors = profile.getProcessors(); - forgeVersion = JsonUtils.fromNonNullJson(Files.readString(fs.getPath(profile.getJson())), GameInstanceManifest.class); + processors = profile.processors(); + forgeVersion = JsonUtils.fromNonNullJson(Files.readString(fs.getPath(profile.json())), GameInstanceManifest.class); - for (Library library : profile.getLibraries()) { + for (Library library : profile.libraries()) { Path file = fs.getPath("maven").resolve(library.getPath()); if (Files.exists(file)) { Path dest = gameRepository.getLayout().getLibraryFile(manifest.id(), library); @@ -326,7 +326,7 @@ public void preExecute() throws Exception { throw new ArtifactMalformedException("Malformed forge installer file", ex); } - dependents.add(new GameLibrariesTask(dependencyManager, manifest, true, profile.getLibraries())); + dependents.add(new GameLibrariesTask(dependencyManager, manifest, true, profile.libraries())); } private Map parseOptions(List args, Map vars) { @@ -354,7 +354,7 @@ private Map parseOptions(List args, Map } private Task patchDownloadMojangMappingsTask(Processor processor, Map vars) { - Map options = parseOptions(processor.getArgs(), vars); + Map options = parseOptions(processor.args(), vars); if (!"DOWNLOAD_MOJMAPS".equals(options.get("task")) || !"client".equals(options.get("side"))) return null; String version = options.get("version"); @@ -424,7 +424,7 @@ public void execute() throws Exception { vars.put("SIDE", "client"); vars.put("MINECRAFT_JAR", FileUtils.getAbsolutePath(isolatedMinecraftJar)); - vars.put("MINECRAFT_VERSION", profile.getMinecraft()); + vars.put("MINECRAFT_VERSION", profile.minecraft()); vars.put("ROOT", FileUtils.getAbsolutePath(gameRepository.getBaseDirectory())); vars.put("INSTALLER", installer.toAbsolutePath().toString()); vars.put("LIBRARY_DIR", FileUtils.getAbsolutePath(gameRepository.getLayout().getLibrariesDirectory())); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java index 6181794c583..1e73bb5cdf8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java @@ -19,7 +19,8 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.VersionMismatchException; -import org.jackhuang.hmcl.download.forge.*; +import org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile; +import org.jackhuang.hmcl.download.forge.ForgeNewInstallTask; import org.jackhuang.hmcl.download.game.GameDownloadTask; import org.jackhuang.hmcl.game.GameComponentType; import org.jackhuang.hmcl.game.GameInstanceManifest; @@ -33,7 +34,9 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; import static org.jackhuang.hmcl.util.StringUtils.removePrefix; import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; @@ -124,14 +127,14 @@ public static Task install( Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); if (GameComponentType.FORGE.getPatchId().equals(installProfile.get("profile")) && (Files.exists(fs.getPath("META-INF/NEOFORGE.RSA")) || installProfileText.contains("neoforge"))) { ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); - if (!gameVersion.equals(profile.getMinecraft())) - throw new VersionMismatchException(profile.getMinecraft(), gameVersion); + if (!gameVersion.equals(profile.minecraft())) + throw new VersionMismatchException(profile.minecraft(), gameVersion); return new GameDownloadTask(dependencyManager, manifest) .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( dependencyManager, manifest, minecraftJar, - modifyNeoForgeOldVersion(gameVersion, profile.getVersion()), + modifyNeoForgeOldVersion(gameVersion, profile.version()), installer)) .thenApplyAsync(neoForgeVersion -> { if (!neoForgeVersion.id().equals(GameComponentType.FORGE.getPatchId()) || neoForgeVersion.version() == null) { @@ -144,14 +147,14 @@ public static Task install( }); } else if (GameComponentType.NEO_FORGE.getPatchId().equals(installProfile.get("profile")) || "NeoForge".equals(installProfile.get("profile"))) { ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); - if (!gameVersion.equals(profile.getMinecraft())) - throw new VersionMismatchException(profile.getMinecraft(), gameVersion); + if (!gameVersion.equals(profile.minecraft())) + throw new VersionMismatchException(profile.minecraft(), gameVersion); return new GameDownloadTask(dependencyManager, manifest) .thenComposeAsync(minecraftJar -> new NeoForgeOldInstallTask( dependencyManager, manifest, minecraftJar, - modifyNeoForgeNewVersion(profile.getVersion()), + modifyNeoForgeNewVersion(profile.version()), installer)); } else { throw new IOException(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java index 522729d4df5..dc9ae474b13 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java @@ -21,9 +21,10 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile; import org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile.Processor; -import org.jackhuang.hmcl.download.game.GameLibrariesTask; import org.jackhuang.hmcl.download.game.GameInstanceJsonDownloadTask; +import org.jackhuang.hmcl.download.game.GameLibrariesTask; import org.jackhuang.hmcl.game.*; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.DigestUtils; @@ -34,7 +35,6 @@ import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.CommandBuilder; -import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.SystemUtils; import org.jetbrains.annotations.NotNull; @@ -53,8 +53,8 @@ import java.util.jar.JarFile; import java.util.zip.ZipException; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.gson.JsonUtils.fromNonNullJson; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class NeoForgeOldInstallTask extends Task { @@ -74,7 +74,7 @@ public void execute() throws Exception { Map outputs = new HashMap<>(); boolean miss = false; - for (Map.Entry entry : processor.getOutputs().entrySet()) { + for (Map.Entry entry : processor.outputs().entrySet()) { String key = entry.getKey(); String value = entry.getValue(); @@ -105,11 +105,11 @@ public void execute() throws Exception { } } - if (!processor.getOutputs().isEmpty() && !miss) { + if (!processor.outputs().isEmpty() && !miss) { return; } - Path jar = gameRepository.getLayout().getArtifactFile(processor.getJar()); + Path jar = gameRepository.getLayout().getArtifactFile(processor.jar()); if (!Files.isRegularFile(jar)) throw new FileNotFoundException("Game processor file not found, should be downloaded in preprocess"); @@ -125,8 +125,8 @@ public void execute() throws Exception { command.add(JavaRuntime.getDefault().getBinary().toString()); command.add("-cp"); - List classpath = new ArrayList<>(processor.getClasspath().size() + 1); - for (Artifact artifact : processor.getClasspath()) { + List classpath = new ArrayList<>(processor.classpath().size() + 1); + for (Artifact artifact : processor.classpath()) { Path file = gameRepository.getLayout().getArtifactFile(artifact); if (!Files.isRegularFile(file)) throw new Exception("Game processor dependency missing"); @@ -137,8 +137,8 @@ public void execute() throws Exception { command.add(mainClass); - List args = new ArrayList<>(processor.getArgs().size()); - for (String arg : processor.getArgs()) { + List args = new ArrayList<>(processor.args().size()); + for (String arg : processor.args()) { String parsed = parseLiteral(arg, vars); if (parsed == null) throw new ArtifactMalformedException("Invalid forge installation configuration"); @@ -147,7 +147,7 @@ public void execute() throws Exception { command.addAll(args); - LOG.info("Executing external processor " + processor.getJar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString()); + LOG.info("Executing external processor " + processor.jar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString()); int exitCode = SystemUtils.callExternalProcess(command); if (exitCode != 0) throw new IOException("Game processor exited abnormally with code " + exitCode); @@ -288,10 +288,10 @@ public boolean doPreExecute() { public void preExecute() throws Exception { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { profile = JsonUtils.fromNonNullJson(Files.readString(fs.getPath("install_profile.json")), ForgeNewInstallProfile.class); - processors = profile.getProcessors(); - neoForgeVersion = JsonUtils.fromNonNullJson(Files.readString(fs.getPath(profile.getJson())), GameInstanceManifest.class); + processors = profile.processors(); + neoForgeVersion = JsonUtils.fromNonNullJson(Files.readString(fs.getPath(profile.json())), GameInstanceManifest.class); - for (Library library : profile.getLibraries()) { + for (Library library : profile.libraries()) { Path file = fs.getPath("maven").resolve(library.getPath()); if (Files.exists(file)) { Path dest = gameRepository.getLayout().getLibraryFile(manifest.id(), library); @@ -310,7 +310,7 @@ public void preExecute() throws Exception { throw new ArtifactMalformedException("Malformed forge installer file", ex); } - dependents.add(new GameLibrariesTask(dependencyManager, manifest, true, profile.getLibraries())); + dependents.add(new GameLibrariesTask(dependencyManager, manifest, true, profile.libraries())); } private Map parseOptions(List args, Map vars) { @@ -338,7 +338,7 @@ private Map parseOptions(List args, Map } private Task patchDownloadMojangMappingsTask(Processor processor, Map vars) { - Map options = parseOptions(processor.getArgs(), vars); + Map options = parseOptions(processor.args(), vars); if (!"DOWNLOAD_MOJMAPS".equals(options.get("task")) || !"client".equals(options.get("side"))) return null; String version = options.get("version"); @@ -408,7 +408,7 @@ public void execute() throws Exception { vars.put("SIDE", "client"); vars.put("MINECRAFT_JAR", FileUtils.getAbsolutePath(isolatedMinecraftJar)); - vars.put("MINECRAFT_VERSION", profile.getMinecraft()); + vars.put("MINECRAFT_VERSION", profile.minecraft()); vars.put("ROOT", FileUtils.getAbsolutePath(gameRepository.getBaseDirectory())); vars.put("INSTALLER", installer.toAbsolutePath().toString()); vars.put("LIBRARY_DIR", FileUtils.getAbsolutePath(gameRepository.getLayout().getLibrariesDirectory())); From 79c46a746fe191cf7044699dea5b87b7a670f05e Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 07:58:06 +0800 Subject: [PATCH 13/34] =?UTF-8?q?=E9=80=B8=E5=85=B4=E9=81=84=E9=A3=9E?= =?UTF-8?q?=E5=82=AC=E8=8A=B1=E4=B8=80=E5=9C=BA=20=E7=8A=B9=E8=AE=B0?= =?UTF-8?q?=E7=9C=8B=E6=9C=B1=E6=88=90=E7=A2=A7=E6=B7=BB=E6=83=86=E6=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/download/forge/ForgeInstallTask.java | 26 ++++------ .../download/forge/ForgeInstallation.java | 51 +++++++++---------- ...ofile.java => ForgeOldInstallProfile.java} | 6 +-- .../download/forge/ForgeOldInstallTask.java | 2 +- 4 files changed, 40 insertions(+), 45 deletions(-) rename HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/{ForgeInstallProfile.java => ForgeOldInstallProfile.java} (85%) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java index f9de720e4a9..2052780f987 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -107,21 +107,17 @@ public void execute() throws IOException, VersionMismatchException, UnsupportedI var type = detectForgeInstallerType(remote.getGameVersion(), installer); switch (type) { - case LEGACY: - dependency = new ForgeLegacyInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); - break; - case OLD: - dependency = new ForgeOldInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); - break; - case NEW: - dependency = new GameDownloadTask(dependencyManager, manifest) - .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( - dependencyManager, - manifest, - minecraftJar, - remote.getSelfVersion(), - installer)); - break; + case LEGACY -> + dependency = new ForgeLegacyInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); + case OLD -> + dependency = new ForgeOldInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); + case NEW -> dependency = new GameDownloadTask(dependencyManager, manifest) + .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( + dependencyManager, + manifest, + minecraftJar, + remote.getSelfVersion(), + installer)); } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java index 5ffbbeea585..036c2e01a39 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java @@ -74,7 +74,7 @@ public static ForgeInstallerType detectForgeInstallerType(String gameVersion, Pa throw new VersionMismatchException(profile.minecraft(), gameVersion); return ForgeInstallerType.NEW; } else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) { - ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class); + ForgeOldInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeOldInstallProfile.class); if (!gameVersion.equals(profile.install().minecraft())) throw new VersionMismatchException(profile.install().minecraft(), gameVersion); return ForgeInstallerType.OLD; @@ -104,32 +104,31 @@ public static Task install( String gameVersion, Path installer) throws IOException, VersionMismatchException, UnsupportedInstallationException { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { - if ((Files.isRegularFile(fs.getPath("mod_MinecraftForge.class")) || Files.isRegularFile(fs.getPath("fmlversion.properties"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { - return new ForgeLegacyInstallTask(dependencyManager, manifest, tryGetLegacyForgeVersion(installer), installer); - } + switch (detectForgeInstallerType(gameVersion, installer)) { + case LEGACY -> { + return new ForgeLegacyInstallTask(dependencyManager, manifest, tryGetLegacyForgeVersion(installer), installer); + } + case OLD -> { + checkCleanroomCompatibility(dependencyManager, manifest, gameVersion); + String installProfileText = Files.readString(fs.getPath("install_profile.json")); + ForgeOldInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeOldInstallProfile.class); - String installProfileText = Files.readString(fs.getPath("install_profile.json")); - Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); - if (installProfile.containsKey("spec")) { - checkCleanroomCompatibility(dependencyManager, manifest, gameVersion); - ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); - if (!gameVersion.equals(profile.minecraft())) - throw new VersionMismatchException(profile.minecraft(), gameVersion); - return new GameDownloadTask(dependencyManager, manifest) - .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( - dependencyManager, - manifest, - minecraftJar, - modifyVersion(gameVersion, profile.version()), - installer)); - } else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) { - checkCleanroomCompatibility(dependencyManager, manifest, gameVersion); - ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class); - if (!gameVersion.equals(profile.install().minecraft())) - throw new VersionMismatchException(profile.install().minecraft(), gameVersion); - return new ForgeOldInstallTask(dependencyManager, manifest, modifyVersion(gameVersion, profile.install().path().getVersion().replaceAll("(?i)forge", "")), installer); - } else { - throw new IOException(); + return new ForgeOldInstallTask(dependencyManager, manifest, modifyVersion(gameVersion, profile.install().path().getVersion().replaceAll("(?i)forge", "")), installer); + } + case NEW -> { + checkCleanroomCompatibility(dependencyManager, manifest, gameVersion); + String installProfileText = Files.readString(fs.getPath("install_profile.json")); + ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); + + return new GameDownloadTask(dependencyManager, manifest) + .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( + dependencyManager, + manifest, + minecraftJar, + modifyVersion(gameVersion, profile.version()), + installer)); + } + default -> throw new IOException(); } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallProfile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallProfile.java similarity index 85% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallProfile.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallProfile.java index fb3f97010b6..5407f28d8e3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallProfile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallProfile.java @@ -27,10 +27,10 @@ /// @author huangyuhui @NotNullByDefault @JsonSerializable -public record ForgeInstallProfile(@SerializedName("install") ForgeInstallManifest install, - @SerializedName("versionInfo") GameInstanceManifest versionInfo) { +public record ForgeOldInstallProfile(@SerializedName("install") ForgeInstallManifest install, + @SerializedName("versionInfo") GameInstanceManifest versionInfo) { - public ForgeInstallProfile { + public ForgeOldInstallProfile { Objects.requireNonNull(install, "install"); Objects.requireNonNull(versionInfo, "versionInfo"); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java index 915f0d96cc6..83a73c42019 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java @@ -67,7 +67,7 @@ public void execute() throws Exception { InputStream stream = zipFile.getInputStream(zipFile.getEntry("install_profile.json")); if (stream == null) throw new ArtifactMalformedException("Malformed forge installer file, install_profile.json does not exist."); - ForgeInstallProfile installProfile = JsonUtils.fromNonNullJsonFully(stream, ForgeInstallProfile.class); + ForgeOldInstallProfile installProfile = JsonUtils.fromNonNullJsonFully(stream, ForgeOldInstallProfile.class); // unpack the universal jar in the installer file. Library forgeLibrary = new Library(installProfile.install().path()); From 9d0cf5e913cf12c3f7edbde61d11f7986e0be75a Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 09:01:36 +0800 Subject: [PATCH 14/34] =?UTF-8?q?=E5=87=B0=E9=9F=B3=E4=B8=80=E7=BB=BD?= =?UTF-8?q?=E6=83=8A=E8=82=83=E6=9C=9D=E7=BA=B2=20=E6=97=A0=E5=AD=97?= =?UTF-8?q?=E7=A9=BA=E7=A2=91=E8=87=AA=E4=B8=8D=E8=B4=9F=E5=80=94=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/instances/InstallerListPage.java | 2 +- .../hmcl/download/forge/ForgeInstallTask.java | 4 +- .../download/forge/ForgeInstallation.java | 16 +++++-- .../download/forge/ForgeInstallerType.java | 3 +- .../forge/ForgeLegacyInstallTask.java | 48 +++++++++++++++---- 5 files changed, 56 insertions(+), 17 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/InstallerListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/InstallerListPage.java index ff38c42dc6f..4abf39871dc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/InstallerListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/InstallerListPage.java @@ -142,7 +142,7 @@ private void reloadCurrentInstance() { public void installOffline() { FileChooser chooser = new FileChooser(); - chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.modloader.installer"), "*.jar", "*.exe")); + chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.modloader.installer"), "*.jar", "*.exe", "*.zip")); Path file = Controllers.showOpenDialog(chooser); if (file != null) doInstallOffline(file); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java index 2052780f987..48898e0d988 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -107,8 +107,8 @@ public void execute() throws IOException, VersionMismatchException, UnsupportedI var type = detectForgeInstallerType(remote.getGameVersion(), installer); switch (type) { - case LEGACY -> - dependency = new ForgeLegacyInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); + case LEGACY_MODLOADER, LEGACY_FML -> + dependency = new ForgeLegacyInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer, type); case OLD -> dependency = new ForgeOldInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); case NEW -> dependency = new GameDownloadTask(dependencyManager, manifest) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java index 036c2e01a39..6b877804be8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java @@ -62,8 +62,12 @@ private ForgeInstallation() { /// @throws VersionMismatchException if the installer targets another Minecraft version public static ForgeInstallerType detectForgeInstallerType(String gameVersion, Path installer) throws IOException, VersionMismatchException { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { - if ((Files.isRegularFile(fs.getPath("mod_MinecraftForge.class")) || Files.isRegularFile(fs.getPath("fmlversion.properties"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { - return ForgeInstallerType.LEGACY; + if ((Files.isRegularFile(fs.getPath("mod_MinecraftForge.class"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { + return ForgeInstallerType.LEGACY_MODLOADER; + } + + if ((Files.isRegularFile(fs.getPath("fmlversion.properties"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { + return ForgeInstallerType.LEGACY_FML; } String installProfileText = Files.readString(fs.getPath("install_profile.json")); @@ -104,9 +108,11 @@ public static Task install( String gameVersion, Path installer) throws IOException, VersionMismatchException, UnsupportedInstallationException { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { - switch (detectForgeInstallerType(gameVersion, installer)) { - case LEGACY -> { - return new ForgeLegacyInstallTask(dependencyManager, manifest, tryGetLegacyForgeVersion(installer), installer); + var type = detectForgeInstallerType(gameVersion, installer); + + switch (type) { + case LEGACY_MODLOADER, LEGACY_FML -> { + return new ForgeLegacyInstallTask(dependencyManager, manifest, tryGetLegacyForgeVersion(installer), installer, type); } case OLD -> { checkCleanroomCompatibility(dependencyManager, manifest, gameVersion); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java index fbab48bd0c0..094948a3662 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java @@ -18,7 +18,8 @@ package org.jackhuang.hmcl.download.forge; public enum ForgeInstallerType { - LEGACY, + LEGACY_MODLOADER, + LEGACY_FML, OLD, NEW } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java index 391dd7561ee..c5d837015f0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java @@ -20,6 +20,7 @@ import org.jackhuang.hmcl.download.ArtifactMalformedException; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.game.*; +import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; import java.io.InputStream; @@ -34,19 +35,26 @@ import java.util.zip.ZipFile; public class ForgeLegacyInstallTask extends Task { + public static final Library MODLOADER_LIBRARY = new Library(new Artifact("modloader", "modloader", "1.1")); + public static final String MODLOADER_DOWNLOAD_URL = "https://github.com/HMCL-dev/metadata/raw/refs/heads/main/fmllibs/ModLoader%201.1.zip"; + public static final Library MODLOADER_MP_LIBRARY = new Library(new Artifact("modloader", "modloader-mp", "1.1")); + public static final String MODLOADER_MP_DOWNLOAD_URL = "https://github.com/HMCL-dev/metadata/raw/refs/heads/main/fmllibs/ModLoaderMP%201.1%20v4.zip"; private final DefaultDependencyManager dependencyManager; private final GameInstanceManifest manifest; private final Path installer; private final String selfVersion; + private final ForgeInstallerType type; private final List> dependencies = new ArrayList<>(1); - ForgeLegacyInstallTask(DefaultDependencyManager dependencyManager, GameInstanceManifest manifest, String selfVersion, Path installer) { + ForgeLegacyInstallTask(DefaultDependencyManager dependencyManager, GameInstanceManifest manifest, String selfVersion, Path installer, ForgeInstallerType type) { this.dependencyManager = dependencyManager; this.manifest = manifest; this.installer = installer; this.selfVersion = selfVersion; + this.type = type; + setName("hmcl.install.forge"); setSignificance(TaskSignificance.MAJOR); } @@ -55,6 +63,27 @@ public List> getDependencies() { return dependencies; } + @Override + public void preExecute() throws Exception { + if (type == ForgeInstallerType.LEGACY_MODLOADER) { + GameRepository gameRepository = dependencyManager.getGameRepository(); + + Path modloaderFile = gameRepository.getLayout().getLibraryFile(manifest.id(), MODLOADER_LIBRARY); + var modloaderDownloadTask = new FileDownloadTask(MODLOADER_DOWNLOAD_URL, modloaderFile, null); + modloaderDownloadTask.setCacheRepository(dependencyManager.getCacheRepository()); + modloaderDownloadTask.setCaching(true); + modloaderDownloadTask.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER); + dependencies.add(modloaderDownloadTask); + + Path modloaderMpFile = gameRepository.getLayout().getLibraryFile(manifest.id(), MODLOADER_MP_LIBRARY); + var modloaderMpDownloadTask = new FileDownloadTask(MODLOADER_MP_DOWNLOAD_URL, modloaderMpFile, null); + modloaderMpDownloadTask.setCacheRepository(dependencyManager.getCacheRepository()); + modloaderMpDownloadTask.setCaching(true); + modloaderMpDownloadTask.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER); + dependencies.add(modloaderMpDownloadTask); + } + } + @Override public boolean doPreExecute() { return true; @@ -80,20 +109,23 @@ public void execute() throws Exception { throw new ArtifactMalformedException("Malformed forge installer file, forgeversion.properties and mod_MinecraftForge.class both does not exist."); } - // unpack the universal jar in the installer file. - Library forgeLibrary = new Library(new Artifact( - "net.minecraftforge", "forge", selfVersion - )); + Library forgeLibrary = new Library(new Artifact("net.minecraftforge", "forge", selfVersion)); GameRepository gameRepository = dependencyManager.getGameRepository(); Path forgeFile = gameRepository.getLayout().getLibraryFile(manifest.id(), forgeLibrary); Files.createDirectories(forgeFile.getParent()); - try (InputStream is = Files.newInputStream(installer); - OutputStream os = Files.newOutputStream(forgeFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + try (InputStream is = Files.newInputStream(installer); OutputStream os = Files.newOutputStream(forgeFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { is.transferTo(os); } - setResult(new GameInstancePatch(GameComponentType.FORGE.getPatchId(), selfVersion, GameInstancePatch.PRIORITY_LOADER, null, null, List.of(forgeLibrary))); + List libraries; + if (type == ForgeInstallerType.LEGACY_MODLOADER) { + libraries = List.of(forgeLibrary, MODLOADER_LIBRARY, MODLOADER_MP_LIBRARY); + } else { + libraries = List.of(forgeLibrary); + } + + setResult(new GameInstancePatch(GameComponentType.FORGE.getPatchId(), selfVersion, GameInstancePatch.PRIORITY_LOADER, null, null, libraries)); } catch (ZipException ex) { throw new ArtifactMalformedException("Malformed forge installer file", ex); } From 3841846070ebf109c72eba8bd98e07583648924b Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 09:05:08 +0800 Subject: [PATCH 15/34] =?UTF-8?q?=E8=B0=81=E4=B8=80=E8=BA=AB=E9=A3=8E?= =?UTF-8?q?=E9=9B=85=E5=A5=89=E5=90=9B=E7=8E=8B=20=E6=8C=81=E7=B4=A0?= =?UTF-8?q?=E5=BF=83=E6=B8=85=E6=98=8E=E7=A7=A4=E5=A4=A9=E4=B8=8B=E6=89=8D?= =?UTF-8?q?=E9=83=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/download/forge/ForgeInstallTask.java | 13 +++-------- .../download/forge/ForgeInstallation.java | 23 ++++--------------- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java index 48898e0d988..a30ffea3f76 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -66,9 +66,7 @@ public boolean doPreExecute() { public void preExecute() throws Exception { installer = Files.createTempFile("forge-installer", ".jar"); - dependent = new FileDownloadTask( - dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()), - installer, null); + dependent = new FileDownloadTask(dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()), installer, null); dependent.setCacheRepository(dependencyManager.getCacheRepository()); dependent.setCaching(true); dependent.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER); @@ -111,13 +109,8 @@ public void execute() throws IOException, VersionMismatchException, UnsupportedI dependency = new ForgeLegacyInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer, type); case OLD -> dependency = new ForgeOldInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); - case NEW -> dependency = new GameDownloadTask(dependencyManager, manifest) - .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( - dependencyManager, - manifest, - minecraftJar, - remote.getSelfVersion(), - installer)); + case NEW -> + dependency = new GameDownloadTask(dependencyManager, manifest).thenComposeAsync(minecraftJar -> new ForgeNewInstallTask(dependencyManager, manifest, minecraftJar, remote.getSelfVersion(), installer)); } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java index 6b877804be8..b3ac48a8913 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java @@ -102,11 +102,7 @@ public static ForgeInstallerType detectForgeInstallerType(String gameVersion, Pa /// unsupported /// @throws VersionMismatchException if the installer targets another Minecraft version /// @throws UnsupportedInstallationException if the manifest already contains Cleanroom - public static Task install( - DefaultDependencyManager dependencyManager, - GameInstanceManifest manifest, - String gameVersion, - Path installer) throws IOException, VersionMismatchException, UnsupportedInstallationException { + public static Task install(DefaultDependencyManager dependencyManager, GameInstanceManifest manifest, String gameVersion, Path installer) throws IOException, VersionMismatchException, UnsupportedInstallationException { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { var type = detectForgeInstallerType(gameVersion, installer); @@ -126,13 +122,7 @@ public static Task install( String installProfileText = Files.readString(fs.getPath("install_profile.json")); ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); - return new GameDownloadTask(dependencyManager, manifest) - .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( - dependencyManager, - manifest, - minecraftJar, - modifyVersion(gameVersion, profile.version()), - installer)); + return new GameDownloadTask(dependencyManager, manifest).thenComposeAsync(minecraftJar -> new ForgeNewInstallTask(dependencyManager, manifest, minecraftJar, modifyVersion(gameVersion, profile.version()), installer)); } default -> throw new IOException(); } @@ -145,13 +135,8 @@ public static Task install( /// @param manifest working manifest receiving the Forge patch /// @param gameVersion Minecraft version used for component analysis /// @throws UnsupportedInstallationException if the manifest already contains Cleanroom - private static void checkCleanroomCompatibility( - DefaultDependencyManager dependencyManager, - GameInstanceManifest manifest, - String gameVersion) throws UnsupportedInstallationException { - GameComponentAnalyzer analyzer = GameComponentAnalyzer.analyze( - dependencyManager.getGameRepository().resolve(manifest), - GameVersionNumber.asGameVersion(gameVersion)); + private static void checkCleanroomCompatibility(DefaultDependencyManager dependencyManager, GameInstanceManifest manifest, String gameVersion) throws UnsupportedInstallationException { + GameComponentAnalyzer analyzer = GameComponentAnalyzer.analyze(dependencyManager.getGameRepository().resolve(manifest), GameVersionNumber.asGameVersion(gameVersion)); if (analyzer.has(GameComponentType.CLEANROOM)) { throw new UnsupportedInstallationException(CLEANROOM_NOT_COMPATIBLE_WITH_FORGE); } From a44e28d4db3a7b054c4aec501fcc854bc86d9215 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 09:06:07 +0800 Subject: [PATCH 16/34] =?UTF-8?q?=E9=81=87=E5=90=88=E9=94=A6=E7=BB=A3?= =?UTF-8?q?=E9=BB=84=E7=B2=B1=20=E7=BA=B7=E7=BA=AD=E4=B8=AD=E9=9D=99?= =?UTF-8?q?=E8=B5=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/download/forge/ForgeInstallTask.java | 6 +++--- .../jackhuang/hmcl/download/forge/ForgeInstallation.java | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java index a30ffea3f76..a277513cab9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -106,11 +106,11 @@ public void execute() throws IOException, VersionMismatchException, UnsupportedI switch (type) { case LEGACY_MODLOADER, LEGACY_FML -> - dependency = new ForgeLegacyInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer, type); + dependency = new ForgeLegacyInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer, type); case OLD -> - dependency = new ForgeOldInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); + dependency = new ForgeOldInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); case NEW -> - dependency = new GameDownloadTask(dependencyManager, manifest).thenComposeAsync(minecraftJar -> new ForgeNewInstallTask(dependencyManager, manifest, minecraftJar, remote.getSelfVersion(), installer)); + dependency = new GameDownloadTask(dependencyManager, manifest).thenComposeAsync(minecraftJar -> new ForgeNewInstallTask(dependencyManager, manifest, minecraftJar, remote.getSelfVersion(), installer)); } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java index b3ac48a8913..05cdc870001 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java @@ -46,12 +46,11 @@ import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class ForgeInstallation { +public final class ForgeInstallation { private ForgeInstallation() { throw new AssertionError(); } - /// Returns whether a Forge installer uses the processor-based format. /// /// @param gameVersion Minecraft version expected by the installation From e30b09d5f2d2c73c470c9228c797530fe8c330f1 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 09:30:08 +0800 Subject: [PATCH 17/34] =?UTF-8?q?=E5=90=8E=E6=9D=A5=E6=9C=89=E4=B8=80?= =?UTF-8?q?=E7=94=9F=E6=98=8E=E6=97=B7=E6=BC=B1=E7=8E=89=E5=99=99=E9=A6=99?= =?UTF-8?q?=20=E4=B9=8C=E6=B1=9F=E6=97=81=E6=AD=8C=E9=9C=B8=E7=8E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jackhuang/hmcl/ui/construct/TaskListPane.java | 3 ++- .../hmcl/download/forge/ForgeLegacyInstallTask.java | 13 +++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index 5e7c90e6b6d..19456a2ce90 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -36,6 +36,7 @@ import org.jackhuang.hmcl.download.cleanroom.CleanroomInstallTask; import org.jackhuang.hmcl.download.fabric.FabricAPIInstallTask; import org.jackhuang.hmcl.download.fabric.FabricInstallTask; +import org.jackhuang.hmcl.download.forge.ForgeLegacyInstallTask; import org.jackhuang.hmcl.download.forge.ForgeNewInstallTask; import org.jackhuang.hmcl.download.forge.ForgeOldInstallTask; import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; @@ -172,7 +173,7 @@ public void onRunning(Task task) { task.setName(i18n("install.installer.install", i18n("install.installer.cleanroom"))); } else if (task instanceof LegacyFabricInstallTask) { task.setName(i18n("install.installer.install", i18n("install.installer.legacyfabric"))); - } else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask) { + } else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask || task instanceof ForgeLegacyInstallTask) { task.setName(i18n("install.installer.install", i18n("install.installer.forge"))); } else if (task instanceof NeoForgeInstallTask || task instanceof NeoForgeOldInstallTask) { task.setName(i18n("install.installer.install", i18n("install.installer.neoforge"))); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java index c5d837015f0..71662815323 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java @@ -22,7 +22,10 @@ import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.DigestUtils; +import org.jetbrains.annotations.Nullable; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; @@ -43,18 +46,20 @@ public class ForgeLegacyInstallTask extends Task { private final DefaultDependencyManager dependencyManager; private final GameInstanceManifest manifest; private final Path installer; - private final String selfVersion; + private final @Nullable String selfVersion; private final ForgeInstallerType type; private final List> dependencies = new ArrayList<>(1); - ForgeLegacyInstallTask(DefaultDependencyManager dependencyManager, GameInstanceManifest manifest, String selfVersion, Path installer, ForgeInstallerType type) { + ForgeLegacyInstallTask(DefaultDependencyManager dependencyManager, GameInstanceManifest manifest, @Nullable String selfVersion, Path installer, ForgeInstallerType type) throws IOException { this.dependencyManager = dependencyManager; this.manifest = manifest; this.installer = installer; - this.selfVersion = selfVersion; + if (selfVersion != null) + this.selfVersion = selfVersion; + else + this.selfVersion = DigestUtils.digestToString("SHA-1", installer); this.type = type; - setName("hmcl.install.forge"); setSignificance(TaskSignificance.MAJOR); } From 278a1ace0dbdbf083276af528a0787da5a23da81 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 09:30:22 +0800 Subject: [PATCH 18/34] =?UTF-8?q?=E8=80=8C=E7=87=95=E7=87=95=E4=BA=8E?= =?UTF-8?q?=E9=A3=9E=E6=80=8E=E7=94=98=E9=9A=8F=E6=B3=A2=E4=BD=8E=E6=98=82?= =?UTF-8?q?=20=E7=9D=A5=E7=9D=A8=E7=87=95=E4=BA=91=E6=9C=88=E4=B8=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java index 71662815323..482c170c8ab 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java @@ -37,7 +37,7 @@ import java.util.zip.ZipException; import java.util.zip.ZipFile; -public class ForgeLegacyInstallTask extends Task { +public final class ForgeLegacyInstallTask extends Task { public static final Library MODLOADER_LIBRARY = new Library(new Artifact("modloader", "modloader", "1.1")); public static final String MODLOADER_DOWNLOAD_URL = "https://github.com/HMCL-dev/metadata/raw/refs/heads/main/fmllibs/ModLoader%201.1.zip"; public static final Library MODLOADER_MP_LIBRARY = new Library(new Artifact("modloader", "modloader-mp", "1.1")); From 8c0baba23402c2c5570f87d3c64ba51349c4c16e Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 09:31:15 +0800 Subject: [PATCH 19/34] =?UTF-8?q?=E8=B0=81=E9=A9=AC=E8=B8=8F=E6=A1=83?= =?UTF-8?q?=E8=8A=B1=E7=94=BB=E8=A7=92=E9=95=BF=E5=90=9F=E9=87=91=E9=BC=93?= =?UTF-8?q?=E9=93=BF=E9=94=B5=20=E5=AF=B8=E5=9C=9F=E4=B8=8D=E8=AE=A9=20?= =?UTF-8?q?=E4=B8=87=E5=86=9B=E4=B8=80=E5=89=91=E6=8C=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java | 2 +- .../org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java | 2 +- .../org/jackhuang/hmcl/download/forge/ForgeRemoteVersion.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java index b8e6fac7ff3..91650add3ca 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java @@ -56,7 +56,7 @@ import static org.jackhuang.hmcl.util.gson.JsonUtils.fromNonNullJson; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class ForgeNewInstallTask extends Task { +public final class ForgeNewInstallTask extends Task { private class ProcessorTask extends Task { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java index 83a73c42019..3c5f5ff3690 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java @@ -34,7 +34,7 @@ import java.util.zip.ZipException; import java.util.zip.ZipFile; -public class ForgeOldInstallTask extends Task { +public final class ForgeOldInstallTask extends Task { private final DefaultDependencyManager dependencyManager; private final GameInstanceManifest manifest; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeRemoteVersion.java index f93607092c0..3cc8e244405 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeRemoteVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeRemoteVersion.java @@ -27,7 +27,7 @@ import java.time.Instant; import java.util.List; -public class ForgeRemoteVersion extends RemoteVersion { +public final class ForgeRemoteVersion extends RemoteVersion { /** * Constructor. * From 971bc4ed588b68ddb61f3eb7dcf0409dc40ab070 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 09:38:51 +0800 Subject: [PATCH 20/34] =?UTF-8?q?=E7=85=8C=E7=85=8C=E5=8D=83=E8=BD=BD?= =?UTF-8?q?=E8=BE=89=E5=85=89=20=E5=8F=A4=E4=BB=8A=E7=9B=9B=E7=AD=B5?= =?UTF-8?q?=E9=95=BF=20=E4=BD=95=E5=B9=B8=E6=9C=89=E4=BD=A0=E7=99=BB?= =?UTF-8?q?=E5=9C=BA=20=E5=85=B1=E4=B8=BE=E9=9C=9E=E8=A7=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java index 91650add3ca..b8e6fac7ff3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java @@ -56,7 +56,7 @@ import static org.jackhuang.hmcl.util.gson.JsonUtils.fromNonNullJson; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public final class ForgeNewInstallTask extends Task { +public class ForgeNewInstallTask extends Task { private class ProcessorTask extends Task { From d5920ad7a20b4b83bc27a4711676e348ba62cd6f Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 09:47:17 +0800 Subject: [PATCH 21/34] =?UTF-8?q?=E8=B0=81=E8=A1=80=E4=BD=9C=E8=83=AD?= =?UTF-8?q?=E8=84=82=E6=9E=AA=E8=B5=B7=E5=A4=AD=E7=9F=AB=E9=BE=99=E7=BF=94?= =?UTF-8?q?=20=E7=BF=A0=E8=A2=96=E8=99=8E=E7=AC=A6=E5=B0=81=E7=96=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jackhuang/hmcl/download/forge/ForgeInstallation.java | 8 ++++---- .../hmcl/download/game/GameVerificationFixTask.java | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java index 05cdc870001..a9e453c9bff 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java @@ -61,14 +61,14 @@ private ForgeInstallation() { /// @throws VersionMismatchException if the installer targets another Minecraft version public static ForgeInstallerType detectForgeInstallerType(String gameVersion, Path installer) throws IOException, VersionMismatchException { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { - if ((Files.isRegularFile(fs.getPath("mod_MinecraftForge.class"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { - return ForgeInstallerType.LEGACY_MODLOADER; - } - if ((Files.isRegularFile(fs.getPath("fmlversion.properties"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { return ForgeInstallerType.LEGACY_FML; } + if ((Files.isRegularFile(fs.getPath("mod_MinecraftForge.class"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { + return ForgeInstallerType.LEGACY_MODLOADER; + } + String installProfileText = Files.readString(fs.getPath("install_profile.json")); Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); if (installProfile.containsKey("spec")) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java index 5a7d4f2f1f5..e83b19b3b42 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java @@ -63,6 +63,9 @@ public void execute() throws IOException { try (FileSystem fs = CompressingUtils.createWritableZipFileSystem(jar, StandardCharsets.UTF_8)) { Files.deleteIfExists(fs.getPath("META-INF/MOJANG_C.DSA")); Files.deleteIfExists(fs.getPath("META-INF/MOJANG_C.SF")); + Files.deleteIfExists(fs.getPath("META-INF/CODESIGN.SF")); + Files.deleteIfExists(fs.getPath("META-INF/CODESIGN.RSA")); + Files.deleteIfExists(fs.getPath("META-INF/MANIFEST.MF")); } } } From bae164133554f76c44b822691a67d97c77536c47 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:59:15 +0800 Subject: [PATCH 22/34] =?UTF-8?q?=E8=B0=81=E5=8F=88=E5=AE=9A=E4=B8=A4?= =?UTF-8?q?=E6=9C=9D=E9=A3=8E=E6=B3=A2=20=E6=8E=8C=E4=B8=89=E4=B8=96?= =?UTF-8?q?=E7=9A=84=E8=8D=A3=E5=85=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../libraries/HMCLLegacyForgeHelper/build.gradle.kts | 10 ++++++++-- .../libraries/HMCLModloaderHelper/build.gradle.kts | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts b/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts index decaecb5afb..6bab68364e0 100644 --- a/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts +++ b/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts @@ -1,8 +1,8 @@ version = "1.0" tasks.compileJava { - sourceCompatibility = "1.8" - targetCompatibility = "1.8" + sourceCompatibility = "1.6" + targetCompatibility = "1.6" } tasks.jar { @@ -16,3 +16,9 @@ tasks.jar { ) } } + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } +} \ No newline at end of file diff --git a/minecraft/libraries/HMCLModloaderHelper/build.gradle.kts b/minecraft/libraries/HMCLModloaderHelper/build.gradle.kts index b4d021d2763..5200cbb1a6f 100644 --- a/minecraft/libraries/HMCLModloaderHelper/build.gradle.kts +++ b/minecraft/libraries/HMCLModloaderHelper/build.gradle.kts @@ -1,8 +1,8 @@ version = "1.0" tasks.compileJava { - sourceCompatibility = "1.8" - targetCompatibility = "1.8" + sourceCompatibility = "1.6" + targetCompatibility = "1.6" } tasks.jar { @@ -16,3 +16,9 @@ tasks.jar { ) } } + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } +} From ec225389b780b858c93ba2f89166a50437327df8 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 11:01:37 +0800 Subject: [PATCH 23/34] =?UTF-8?q?=E4=BB=8A=E5=A4=9C=E6=88=91=E4=B8=BA?= =?UTF-8?q?=E4=BD=A0=E8=BD=BB=E5=8F=99=E5=B7=BE=E5=B8=BC=E8=8A=AC=E8=8A=B3?= =?UTF-8?q?=20=E6=B5=A9=E6=AD=8C=E4=B8=80=E6=9B=B2=E4=BC=A0=E9=A6=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java index 3b2b5179765..9dea405f7ca 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java @@ -172,8 +172,6 @@ public void makeLaunchScript(Path scriptFile) throws IOException { protected void appendJvmArgs(CommandBuilder result) { super.appendJvmArgs(result); - if (!options.isAllowAutoAgent() && !options.isNoGeneratedJVMArgs()) return; - if (this.instance.getVersion().compareTo("1.6") < 0 && this.instance.hasComponent(GameComponentType.FORGE)) { LOG.info("Attempting to patch game with legacy-forge-helper"); try { @@ -192,6 +190,8 @@ protected void appendJvmArgs(CommandBuilder result) { } } + if (!options.isAllowAutoAgent() && !options.isNoGeneratedJVMArgs()) return; + if (!options.isNoGeneratedOptimizingJVMArgs() && NativePatcher.needPatchMemoryUtil(manifest, options.getJava().getParsedVersion())) { LOG.info("Attempting to patch game with lwjgl-unsafe-agent"); From dd99e8ba8fa5fb2a473ea14a01b918fc5cb24f02 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 11:15:46 +0800 Subject: [PATCH 24/34] =?UTF-8?q?=E5=94=B1=E4=B8=8D=E5=AE=8C=E8=B7=8C?= =?UTF-8?q?=E5=AE=95=E6=98=8E=E5=AA=9A=E5=88=9A=E5=BC=BA=20=E7=99=BE?= =?UTF-8?q?=E4=BB=A3=E7=A7=BE=E5=8D=8E=E5=8D=83=E7=A7=8B=E6=A6=9C=E6=A0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../libraries/HMCLLegacyForgeHelper/build.gradle.kts | 8 ++++---- .../libraries/HMCLModloaderHelper/build.gradle.kts | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts b/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts index 6bab68364e0..27a42576a1c 100644 --- a/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts +++ b/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts @@ -17,8 +17,8 @@ tasks.jar { } } -java { - toolchain { - languageVersion = JavaLanguageVersion.of(8) - } +tasks.compileJava { + javaCompiler.set(javaToolchains.compilerFor { + languageVersion.set(JavaLanguageVersion.of(8)) + }) } \ No newline at end of file diff --git a/minecraft/libraries/HMCLModloaderHelper/build.gradle.kts b/minecraft/libraries/HMCLModloaderHelper/build.gradle.kts index 5200cbb1a6f..dfd46033798 100644 --- a/minecraft/libraries/HMCLModloaderHelper/build.gradle.kts +++ b/minecraft/libraries/HMCLModloaderHelper/build.gradle.kts @@ -17,8 +17,8 @@ tasks.jar { } } -java { - toolchain { - languageVersion = JavaLanguageVersion.of(8) - } -} +tasks.compileJava { + javaCompiler.set(javaToolchains.compilerFor { + languageVersion.set(JavaLanguageVersion.of(8)) + }) +} \ No newline at end of file From d307940a859a02cd5de6b0002e9ee964cf233201 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Fri, 28 Aug 2026 12:28:15 +0800 Subject: [PATCH 25/34] =?UTF-8?q?=E8=A1=8C=E7=BB=8F=E6=95=85=E6=A2=A6?= =?UTF-8?q?=E4=BB=8A=E5=8F=88=E6=9D=A5=E8=AE=BF=20=E5=A4=A9=E9=AB=98?= =?UTF-8?q?=E6=B5=B7=E9=98=94=E7=9B=9B=E4=B8=96=E9=A3=9E=E6=89=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 +- .../jackhuang/hmcl/game/HMCLGameLauncher.java | 6 +- HMCLCore/build.gradle.kts | 2 +- .../hmcl/download/game/GameLibrariesTask.java | 84 +++++-------------- .../build.gradle.kts | 2 +- .../jackhuang/hmcl/HMCLModLoaderHelper.java} | 4 +- settings.gradle.kts | 2 +- 7 files changed, 32 insertions(+), 74 deletions(-) rename minecraft/libraries/{HMCLModloaderHelper => HMCLModLoaderHelper}/build.gradle.kts (89%) rename minecraft/libraries/{HMCLModloaderHelper/src/main/java/org/jackhuang/hmcl/HMCLModloaderHelper.java => HMCLModLoaderHelper/src/main/java/org/jackhuang/hmcl/HMCLModLoaderHelper.java} (98%) diff --git a/.gitignore b/.gitignore index c0214f06aab..82850c8a91c 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,7 @@ language-subtag-registry /minecraft/libraries/HMCLTransformerDiscoveryService/build/ /minecraft/libraries/HMCLMultiMCBootstrap/build/ /minecraft/libraries/HMCLLegacyForgeHelper/build/ -/minecraft/libraries/HMCLModloaderHelper/build/ +/minecraft/libraries/HMCLModLoaderHelper/build/ /buildSrc/build/ # idea @@ -45,7 +45,7 @@ language-subtag-registry /minecraft/libraries/HMCLTransformerDiscoveryService/out/ /minecraft/libraries/HMCLMultiMCBootstrap/out/ /minecraft/libraries/HMCLLegacyForgeHelper/out/ -/minecraft/libraries/HMCLModloaderHelper/out/ +/minecraft/libraries/HMCLModLoaderHelper/out/ # eclipse /bin/ @@ -55,7 +55,7 @@ language-subtag-registry /minecraft/libraries/HMCLTransformerDiscoveryService/bin/ /minecraft/libraries/HMCLMultiMCBootstrap/bin/ /minecraft/libraries/HMCLLegacyForgeHelper/bin/ -/minecraft/libraries/HMCLModloaderHelper/bin/ +/minecraft/libraries/HMCLModLoaderHelper/bin/ .classpath .project .settings diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java index 9dea405f7ca..7b5566d1c2a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java @@ -184,7 +184,7 @@ protected void appendJvmArgs(CommandBuilder result) { if (this.instance.getVersion().compareTo("1.2") < 0 && this.instance.hasComponent(GameComponentType.FORGE)) { LOG.info("Attempting to patch game with modloader-helper"); try { - result.add("-javaagent:" + extractModloaderHelper()); + result.add("-javaagent:" + extractModLoaderHelper()); } catch (Exception e) { LOG.warning("Failed to extract modloader-helper", e); } @@ -274,9 +274,9 @@ private Path extractLegacyForgeHelper() throws IOException { return agentPath; } - private Path extractModloaderHelper() throws IOException { + private Path extractModLoaderHelper() throws IOException { Library library = new Library(new Artifact("org.jackhuang.hmcl", "modloader-helper", "1.0")); - String fileName = "HMCLModloaderHelper-1.0.jar"; + String fileName = "HMCLModLoaderHelper-1.0.jar"; Path agentPath = instance.getLayout().getLibraryFile(instance.getId(), library).toAbsolutePath().normalize(); if (agentPath.toString().contains("=")) { diff --git a/HMCLCore/build.gradle.kts b/HMCLCore/build.gradle.kts index 43c067aa88e..4061d4299d3 100644 --- a/HMCLCore/build.gradle.kts +++ b/HMCLCore/build.gradle.kts @@ -45,7 +45,7 @@ tasks.processResources { listOf( "HMCLTransformerDiscoveryService", "HMCLMultiMCBootstrap", - "HMCLModloaderHelper", + "HMCLModLoaderHelper", "HMCLLegacyForgeHelper" ).map { project(":$it").tasks["jar"] as Jar }.forEach { task -> dependsOn(task) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java index 6d817040d72..005c3e23e90 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java @@ -18,10 +18,10 @@ package org.jackhuang.hmcl.download.game; import org.jackhuang.hmcl.download.AbstractDependencyManager; +import org.jackhuang.hmcl.download.forge.ForgeLegacyInstallTask; import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.util.DigestUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; @@ -30,7 +30,6 @@ import java.io.IOException; import java.io.InputStream; -import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; @@ -119,48 +118,18 @@ public static boolean shouldDownloadLibrary(GameRepository gameRepository, GameI return false; } - private static boolean shouldDownloadFMLLib(FMLLib fmlLib, Path file) { - if (!Files.isRegularFile(file)) - return true; - - try { - return !DigestUtils.digestToString("SHA-1", file).equalsIgnoreCase(fmlLib.sha1); - } catch (IOException e) { - LOG.warning("Unable to calc hash value of file " + file, e); - return true; - } - } - /// {@inheritDoc} @Override public void execute() throws IOException { int progress = 0; GameRepository gameRepository = dependencyManager.getGameRepository(); for (Library library : libraries) { + boolean handled = false; + if (!library.appliesToCurrentEnvironment()) { continue; } - // https://github.com/HMCL-dev/HMCL/issues/3975 - if (library.is("net.minecraftforge", "minecraftforge") - && gameRepository instanceof DefaultGameRepository defaultGameRepository) { - List fmlLibs = getFMLLibs(library.version()); - if (fmlLibs != null) { - Path libDir = defaultGameRepository.getBaseDirectory().resolve("lib") - .toAbsolutePath().normalize(); - - for (FMLLib fmlLib : fmlLibs) { - Path file = libDir.resolve(fmlLib.name); - if (shouldDownloadFMLLib(fmlLib, file)) { - List uris = dependencyManager.getDownloadProvider() - .injectURLWithCandidates(fmlLib.downloadUrl()); - dependencies.add(new FileDownloadTask(uris, file) - .withCounter("hmcl.install.libraries")); - } - } - } - } - Path file = gameRepository.getLayout().getLibraryFile(manifest.id(), library); if ("optifine".equals(library.groupId()) && Files.exists(file)) { if (Files.exists(file) && libraries.stream().filter(it -> it.is("optifine", "OptiFine")) @@ -183,6 +152,7 @@ public void execute() throws IOException { "Bundled HMCLMultiMCBootstrap is missing.")) { Files.createDirectories(file.getParent()); Files.copy(input, file, StandardCopyOption.REPLACE_EXISTING); + handled = true; } } } else if (library.is("org.jackhuang.hmcl", "transformer-discovery-service")) { @@ -192,10 +162,26 @@ public void execute() throws IOException { "Bundled HMCLTransformerDiscoveryService is missing.")) { Files.createDirectories(file.getParent()); Files.copy(input, file, StandardCopyOption.REPLACE_EXISTING); + handled = true; + } + } else if (library.groupId().equals("modloader")) { + String url = switch (library.artifactId()) { + case "modloader" -> ForgeLegacyInstallTask.MODLOADER_DOWNLOAD_URL; + case "modloader-mp" -> ForgeLegacyInstallTask.MODLOADER_MP_DOWNLOAD_URL; + default -> null; + }; + if (url != null) { + var fileDownloadTask = new FileDownloadTask(url, file, null); + fileDownloadTask.setCacheRepository(dependencyManager.getCacheRepository()); + fileDownloadTask.setCaching(true); + fileDownloadTask.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER); + dependencies.add(fileDownloadTask.withCounter("hmcl.install.libraries")); + + handled = true; } } - if (shouldDownloadLibrary(gameRepository, manifest, library, integrityCheck) && (library.hasDownloadURL() || !"optifine".equals(library.groupId()))) { + if (!handled && shouldDownloadLibrary(gameRepository, manifest, library, integrityCheck) && (library.hasDownloadURL() || !"optifine".equals(library.groupId()))) { dependencies.add(new LibraryDownloadTask(dependencyManager, file, library).withCounter("hmcl.install.libraries")); } else { dependencyManager.getCacheRepository().tryCacheLibrary(library, file); @@ -209,32 +195,4 @@ public void execute() throws IOException { notifyPropertiesChanged(); } } - - private static @Nullable List getFMLLibs(String forgeVersion) { - if (forgeVersion == null) - return null; - - // Minecraft 1.5.2 - if (forgeVersion.startsWith("7.8.1.")) { - return List.of( - new FMLLib("argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"), - new FMLLib("guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", - "https://repo1.maven.org/maven2/com/google/guava/guava/14.0-rc3/guava-14.0-rc3.jar"), - new FMLLib("asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", - "https://repo1.maven.org/maven2/org/ow2/asm/asm-all/4.1/asm-all-4.1.jar"), - new FMLLib("bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", - "https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.48/bcprov-jdk15on-1.48.jar"), - new FMLLib("deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9"), - new FMLLib("scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85") - ); - } - - return null; - } - - private record FMLLib(String name, String sha1, String downloadUrl) { - FMLLib(String name, String sha1) { - this(name, sha1, "https://hmcl.glavo.site/metadata/fmllibs/" + name); - } - } } diff --git a/minecraft/libraries/HMCLModloaderHelper/build.gradle.kts b/minecraft/libraries/HMCLModLoaderHelper/build.gradle.kts similarity index 89% rename from minecraft/libraries/HMCLModloaderHelper/build.gradle.kts rename to minecraft/libraries/HMCLModLoaderHelper/build.gradle.kts index dfd46033798..0387951b9cd 100644 --- a/minecraft/libraries/HMCLModloaderHelper/build.gradle.kts +++ b/minecraft/libraries/HMCLModLoaderHelper/build.gradle.kts @@ -10,7 +10,7 @@ tasks.jar { attributes( "Created-By" to "Copyright(c) 2026 huangyuhui.", "Implementation-Version" to project.version, - "Premain-Class" to "org.jackhuang.hmcl.HMCLModloaderHelper", + "Premain-Class" to "org.jackhuang.hmcl.HMCLModLoaderHelper", "Can-Redefine-Classes" to true, "Can-Retransform-Classes" to true ) diff --git a/minecraft/libraries/HMCLModloaderHelper/src/main/java/org/jackhuang/hmcl/HMCLModloaderHelper.java b/minecraft/libraries/HMCLModLoaderHelper/src/main/java/org/jackhuang/hmcl/HMCLModLoaderHelper.java similarity index 98% rename from minecraft/libraries/HMCLModloaderHelper/src/main/java/org/jackhuang/hmcl/HMCLModloaderHelper.java rename to minecraft/libraries/HMCLModLoaderHelper/src/main/java/org/jackhuang/hmcl/HMCLModLoaderHelper.java index e0087ff5ca0..e43c73d5bbc 100644 --- a/minecraft/libraries/HMCLModloaderHelper/src/main/java/org/jackhuang/hmcl/HMCLModloaderHelper.java +++ b/minecraft/libraries/HMCLModLoaderHelper/src/main/java/org/jackhuang/hmcl/HMCLModLoaderHelper.java @@ -27,8 +27,8 @@ import java.security.CodeSource; import java.security.ProtectionDomain; -public final class HMCLModloaderHelper { - private HMCLModloaderHelper() { +public final class HMCLModLoaderHelper { + private HMCLModLoaderHelper() { throw new AssertionError(); } diff --git a/settings.gradle.kts b/settings.gradle.kts index 475625a8e4d..bf7564a31ee 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,7 +6,7 @@ include( ) val minecraftLibraries = - listOf("HMCLTransformerDiscoveryService", "HMCLMultiMCBootstrap", "HMCLLegacyForgeHelper", "HMCLModloaderHelper") + listOf("HMCLTransformerDiscoveryService", "HMCLMultiMCBootstrap", "HMCLLegacyForgeHelper", "HMCLModLoaderHelper") include(minecraftLibraries) for (library in minecraftLibraries) { From 9c630cc6833caced890d73b337632a57c1d80b25 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Sun, 30 Aug 2026 09:09:53 +0800 Subject: [PATCH 26/34] =?UTF-8?q?=E5=86=8D=E8=B0=B1=E5=8D=81=E4=B8=87?= =?UTF-8?q?=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/download/forge/ForgeLegacyInstallTask.java | 4 ++-- .../hmcl/download/forge/ForgeVersionList.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java index 482c170c8ab..5151f74efe7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java @@ -39,9 +39,9 @@ public final class ForgeLegacyInstallTask extends Task { public static final Library MODLOADER_LIBRARY = new Library(new Artifact("modloader", "modloader", "1.1")); - public static final String MODLOADER_DOWNLOAD_URL = "https://github.com/HMCL-dev/metadata/raw/refs/heads/main/fmllibs/ModLoader%201.1.zip"; + public static final String MODLOADER_DOWNLOAD_URL = "https://hmcl.glavo.site/metadata/fmllibs/ModLoader%201.1.zip"; public static final Library MODLOADER_MP_LIBRARY = new Library(new Artifact("modloader", "modloader-mp", "1.1")); - public static final String MODLOADER_MP_DOWNLOAD_URL = "https://github.com/HMCL-dev/metadata/raw/refs/heads/main/fmllibs/ModLoaderMP%201.1%20v4.zip"; + public static final String MODLOADER_MP_DOWNLOAD_URL = "https://hmcl.glavo.site/metadata/fmllibs/ModLoaderMP%201.1%20v4.zip"; private final DefaultDependencyManager dependencyManager; private final GameInstanceManifest manifest; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java index c9f49822fe3..6bfda41c2e0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java @@ -70,23 +70,23 @@ public Task refreshAsync() { ForgeVersion version = root.number().get(v); if (version == null) continue; - String jar = null; + String installer = null; for (String[] file : version.files()) - if (file.length > 1 && "installer".equals(file[1])) { + if (file.length > 1) { String classifier = version.mcversion() + "-" + version.version() + (StringUtils.isNotBlank(version.branch()) ? "-" + version.branch() : ""); String fileName = root.artifact() + "-" + classifier + "-" + file[1] + "." + file[0]; - jar = root.webpath() + classifier + "/" + fileName; + installer = root.webpath() + classifier + "/" + fileName; } - if (jar == null) + if (installer == null) continue; versions.put(gameVersion, new ForgeRemoteVersion( toLookupVersion(version.mcversion()), version.version(), version.modified() > 0 ? Instant.ofEpochSecond(version.modified()) : null, - Collections.singletonList(jar) + Collections.singletonList(installer) )); } } From 35d3fda30df2fade2cda4ae255edd4d4b1675cb9 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Sun, 30 Aug 2026 18:31:22 +0800 Subject: [PATCH 27/34] update --- .../src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java b/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java index 8b51ec04b30..72735396ee0 100644 --- a/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java +++ b/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java @@ -30,7 +30,7 @@ private HMCLLegacyForgeHelper() { private static final String TARGET_URL = "http://files.minecraftforge.net/fmllibs/%s"; - private static String newRootUrl = "https://https://hmcl.glavo.site/metadata/fmllibs/%s"; + private static String newRootUrl = "https://hmcl.glavo.site/metadata/fmllibs/%s"; public static void premain(String agentArgs, Instrumentation inst) { if (agentArgs != null && !agentArgs.trim().isEmpty()) { From 981ad6cf753c5194c39933a55430a85160668836 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Sun, 30 Aug 2026 18:36:02 +0800 Subject: [PATCH 28/34] update --- .../jackhuang/hmcl/HMCLLegacyForgeHelper.java | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java b/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java index 72735396ee0..84749b8c3a7 100644 --- a/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java +++ b/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java @@ -19,16 +19,22 @@ import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.List; -@SuppressWarnings("JavaPrintToLogpoint") public final class HMCLLegacyForgeHelper { private HMCLLegacyForgeHelper() { throw new AssertionError(); } - private static final String TARGET_URL = "http://files.minecraftforge.net/fmllibs/%s"; + private static final List TARGET_URL_LIST = new ArrayList(); + + static { + TARGET_URL_LIST.add("http://files.minecraftforge.net/fmllibs/%s"); + TARGET_URL_LIST.add("https://files.minecraftforge.net/fmllibs/%s"); + } private static String newRootUrl = "https://hmcl.glavo.site/metadata/fmllibs/%s"; @@ -55,8 +61,13 @@ public byte[] transform(ClassLoader loader, String className, Class classBein if ("cpw/mods/fml/relauncher/CoreFMLLibraries".equals(className) || "cpw.mods.fml.relauncher.CoreFMLLibraries".equals(className)) { try { System.out.println("[LegacyForgeHelper] Transforming " + className + " ..."); - byte[] modified = patchConstantPoolUtf8(classfileBuffer, TARGET_URL, newRootUrl); - System.out.println("[LegacyForgeHelper] Successfully patched RootURL in " + className); + + byte[] modified = classfileBuffer; + for (String target : TARGET_URL_LIST) { + modified = patchConstantPoolUtf8(modified, target, newRootUrl); + System.out.println("[LegacyForgeHelper] Successfully patched RootURL in " + className); + } + return modified; } catch (Throwable t) { System.err.println("[LegacyForgeHelper] Failed to patch class"); @@ -90,11 +101,11 @@ private static byte[] patchConstantPoolUtf8(byte[] classBytes, String targetStr, case 1: // CONSTANT_Utf8 int utf8LengthPos = pos; int len = ((classBytes[pos++] & 0xFF) << 8) | (classBytes[pos++] & 0xFF); - String str = new String(classBytes, pos, len, StandardCharsets.UTF_8); + String str = new String(classBytes, pos, len, Charset.forName("UTF-8")); pos += len; if (targetStr.equals(str)) { - byte[] replacementBytes = replacementStr.getBytes(StandardCharsets.UTF_8); + byte[] replacementBytes = replacementStr.getBytes(Charset.forName("UTF-8")); if (replacementBytes.length > 65535) { throw new IllegalArgumentException("Replacement URL is too long (max 65535 bytes)"); } From bdac67bf85edfe1bb65b10889c0d9a05325487f6 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Sun, 30 Aug 2026 18:40:30 +0800 Subject: [PATCH 29/34] update --- .../jackhuang/hmcl/HMCLLegacyForgeHelper.java | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java b/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java index 84749b8c3a7..4e6e054603f 100644 --- a/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java +++ b/minecraft/libraries/HMCLLegacyForgeHelper/src/main/java/org/jackhuang/hmcl/HMCLLegacyForgeHelper.java @@ -21,20 +21,14 @@ import java.lang.instrument.Instrumentation; import java.nio.charset.Charset; import java.security.ProtectionDomain; -import java.util.ArrayList; -import java.util.List; +@SuppressWarnings("JavaPrintToLogpoint") public final class HMCLLegacyForgeHelper { private HMCLLegacyForgeHelper() { throw new AssertionError(); } - private static final List TARGET_URL_LIST = new ArrayList(); - - static { - TARGET_URL_LIST.add("http://files.minecraftforge.net/fmllibs/%s"); - TARGET_URL_LIST.add("https://files.minecraftforge.net/fmllibs/%s"); - } + private static final String TARGET_URL = "http://files.minecraftforge.net/fmllibs/%s"; private static String newRootUrl = "https://hmcl.glavo.site/metadata/fmllibs/%s"; @@ -61,13 +55,8 @@ public byte[] transform(ClassLoader loader, String className, Class classBein if ("cpw/mods/fml/relauncher/CoreFMLLibraries".equals(className) || "cpw.mods.fml.relauncher.CoreFMLLibraries".equals(className)) { try { System.out.println("[LegacyForgeHelper] Transforming " + className + " ..."); - - byte[] modified = classfileBuffer; - for (String target : TARGET_URL_LIST) { - modified = patchConstantPoolUtf8(modified, target, newRootUrl); - System.out.println("[LegacyForgeHelper] Successfully patched RootURL in " + className); - } - + byte[] modified = patchConstantPoolUtf8(classfileBuffer, TARGET_URL, newRootUrl); + System.out.println("[LegacyForgeHelper] Successfully patched RootURL in " + className); return modified; } catch (Throwable t) { System.err.println("[LegacyForgeHelper] Failed to patch class"); From 58e73c448ed8131aba635884ffa11e89d7f6c4e2 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Sun, 30 Aug 2026 18:45:08 +0800 Subject: [PATCH 30/34] update --- .../download/forge/ForgeBMCLVersionList.java | 10 ++-------- .../hmcl/download/forge/ForgeInstallation.java | 16 ++++++++++++++++ .../hmcl/download/forge/ForgeVersionList.java | 11 +++-------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java index 6aae6fa7902..ad4c526444c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java @@ -34,6 +34,8 @@ import java.util.List; import java.util.Optional; +import static org.jackhuang.hmcl.download.forge.ForgeInstallation.fromLookupVersion; +import static org.jackhuang.hmcl.download.forge.ForgeInstallation.toLookupVersion; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; @@ -59,14 +61,6 @@ public Task refreshAsync() { throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list."); } - private static String toLookupVersion(String gameVersion) { - return "1.7.10-pre4".equals(gameVersion) ? "1.7.10_pre4" : gameVersion; - } - - private static String fromLookupVersion(String lookupVersion) { - return "1.7.10_pre4".equals(lookupVersion) ? "1.7.10-pre4" : lookupVersion; - } - private static String toLookupBranch(String gameVersion, String branch) { if ("1.7.10-pre4".equals(gameVersion)) { return "prerelease"; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java index a9e453c9bff..c3b00255554 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java @@ -170,4 +170,20 @@ private static String modifyVersion(String gameVersion, String version) { return null; } } + + public static String toLookupVersion(String gameVersion) { + return switch (gameVersion) { + case "1.7.10-pre4" -> "1.7.10_pre4"; + case "1.4" -> "1.4.0"; + default -> gameVersion; + }; + } + + public static String fromLookupVersion(String gameVersion) { + return switch (gameVersion) { + case "1.7.10_pre4" -> "1.7.10-pre4"; + case "1.4.0" -> "1.4"; + default -> gameVersion; + }; + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java index 6bfda41c2e0..794477f8fb8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java @@ -29,6 +29,9 @@ import java.util.Collections; import java.util.Map; +import static org.jackhuang.hmcl.download.forge.ForgeInstallation.fromLookupVersion; +import static org.jackhuang.hmcl.download.forge.ForgeInstallation.toLookupVersion; + /** * * @author huangyuhui @@ -45,14 +48,6 @@ public boolean hasType() { return false; } - private static String toLookupVersion(String gameVersion) { - return "1.7.10-pre4".equals(gameVersion) ? "1.7.10_pre4" : gameVersion; - } - - private static String fromLookupVersion(String lookupVersion) { - return "1.7.10_pre4".equals(lookupVersion) ? "1.7.10-pre4" : lookupVersion; - } - @Override public Task refreshAsync() { return new GetTask(FORGE_LIST).thenGetJsonAsync(ForgeVersionRoot.class) From 895fe68331cee2728d6f6fcd95a1e304cc217a14 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Sun, 30 Aug 2026 18:46:43 +0800 Subject: [PATCH 31/34] update --- .../jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java index ad4c526444c..924c28a60d1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java @@ -24,6 +24,7 @@ import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.gson.JsonSerializable; import org.jackhuang.hmcl.util.gson.Validation; import org.jackhuang.hmcl.util.io.NetworkUtils; @@ -128,6 +129,7 @@ public Optional getVersion(String gameVersion, String remote } @Immutable + @JsonSerializable public record ForgeVersion(String branch, int build, String mcversion, String modified, String version, List files) implements Validation { @@ -150,6 +152,7 @@ public void validate() throws JsonParseException { } @Immutable + @JsonSerializable public record File(String format, String category, String hash) { public File() { this("", "", ""); From aa988525cc2065fdd406c983d80354479f4798fe Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Sun, 30 Aug 2026 19:40:35 +0800 Subject: [PATCH 32/34] update --- .../hmcl/addon/meta/ForgeOldModMetadata.java | 11 ++- .../hmcl/addon/mod/LocalModFile.java | 39 +++++++-- .../download/forge/ForgeInstallation.java | 37 +-------- .../forge/ForgeLegacyInstallProfile.java | 79 +++++++++++++++++++ .../hmcl/download/game/GameLibrariesTask.java | 2 +- 5 files changed, 122 insertions(+), 46 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallProfile.java diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java index 81f358c94e6..1861385987c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java @@ -30,6 +30,7 @@ import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.tree.ZipFileTree; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.nio.file.Path; @@ -124,8 +125,12 @@ public String[] getAuthors() { return authors; } - public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException { - ZipArchiveEntry mcmod = tree.getEntry("mcmod.info"); + public static LocalModFile fromFile(@Nullable ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException { + return fromFile(modManager, modFile, tree, "mcmod.info"); + } + + public static LocalModFile fromFile(@Nullable ModManager modManager, Path modFile, ZipFileTree tree, String infoFileName) throws IOException, JsonParseException { + ZipArchiveEntry mcmod = tree.getEntry(infoFileName); if (mcmod == null) throw new IOException("File " + modFile + " is not a Forge mod."); @@ -157,7 +162,7 @@ else if (firstToken == JsonToken.BEGIN_OBJECT) { authors = String.join(", ", metadata.getAuthorList()); if (StringUtils.isBlank(authors)) authors = metadata.getCredits(); - return new LocalModFile(modManager, modManager.getLocalMod(metadata.getModId(), ModLoaderType.FORGE), modFile, metadata.getName(), new LocalAddonFile.Description(metadata.getDescription()), + return new LocalModFile(modManager, modManager == null ? null : modManager.getLocalMod(metadata.getModId(), ModLoaderType.FORGE), modFile, metadata.getName(), new LocalAddonFile.Description(metadata.getDescription()), authors, metadata.getVersion(), metadata.getGameVersion(), StringUtils.isBlank(metadata.getUrl()) ? metadata.getUpdateUrl() : metadata.url, metadata.getLogoFile()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java index 32984fa3baa..85248076b10 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java @@ -25,11 +25,15 @@ import org.jackhuang.hmcl.addon.RemoteAddonRepository; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -40,7 +44,9 @@ public final class LocalModFile extends LocalAddonFile implements Comparable { private Path file; + @Nullable private final ModManager modManager; + @Nullable private final LocalMod mod; private final String name; private final Description description; @@ -52,11 +58,11 @@ public final class LocalModFile extends LocalAddonFile implements Comparable install(DefaultDependencyManager dependenc switch (type) { case LEGACY_MODLOADER, LEGACY_FML -> { - return new ForgeLegacyInstallTask(dependencyManager, manifest, tryGetLegacyForgeVersion(installer), installer, type); + var forgeProfile = ForgeLegacyInstallProfile.parse(installer); + return new ForgeLegacyInstallTask(dependencyManager, manifest, forgeProfile != null ? forgeProfile.forgeVersion() : null, installer, type); } case OLD -> { checkCleanroomCompatibility(dependencyManager, manifest, gameVersion); @@ -145,32 +140,6 @@ private static String modifyVersion(String gameVersion, String version) { return removePrefix(removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_"), "forge-"); } - public static @Nullable String tryGetLegacyForgeVersion(Path path) { - try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(path)) { - if (Files.isRegularFile(fs.getPath("mod_MinecraftForge.info"))) { - ForgeOldModMetadata metadata = JsonUtils.fromNonNullJson(Files.readString(fs.getPath("mod_MinecraftForge.info")), ForgeOldModMetadata.class); - return metadata.getVersion(); - } - - if (Files.isRegularFile(fs.getPath("forgeversion.properties"))) { - Properties properties = new Properties(); - properties.load(Files.newInputStream(fs.getPath("forgeversion.properties"))); - List list = new ArrayList<>(); - list.add(properties.getProperty("forge.major.number")); - list.add(properties.getProperty("forge.minor.number")); - list.add(properties.getProperty("forge.revision.number")); - list.add(properties.getProperty("forge.build.number")); - return String.join(".", list); - } - - return null; - - } catch (IOException e) { - LOG.warning("Failed to get legacy forge version", e); - return null; - } - } - public static String toLookupVersion(String gameVersion) { return switch (gameVersion) { case "1.7.10-pre4" -> "1.7.10_pre4"; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallProfile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallProfile.java new file mode 100644 index 00000000000..da60d46473b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallProfile.java @@ -0,0 +1,79 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui 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 . + */ +package org.jackhuang.hmcl.download.forge; + +import kala.compress.archivers.zip.ZipArchiveReader; +import org.jackhuang.hmcl.addon.meta.ForgeOldModMetadata; +import org.jackhuang.hmcl.addon.mod.LocalModFile; +import org.jackhuang.hmcl.util.io.CompressingUtils; +import org.jackhuang.hmcl.util.tree.ZipFileTree; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +public record ForgeLegacyInstallProfile(@Nullable String gameVersion, @Nullable String forgeVersion) { + @Nullable + public static ForgeLegacyInstallProfile parse(Path forgeArchive) { + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(forgeArchive)) { + String gameVersion = null; + String forgeVersion = null; + + if (Files.isRegularFile(fs.getPath("fmlversion.properties"))) { + Properties properties = new Properties(); + properties.load(Files.newInputStream(fs.getPath("fmlversion.properties"))); + + gameVersion = properties.getProperty("fmlbuild.mcversion"); + } + + if (Files.isRegularFile(fs.getPath("forgeversion.properties"))) { + Properties properties = new Properties(); + properties.load(Files.newInputStream(fs.getPath("forgeversion.properties"))); + List list = new ArrayList<>(); + list.add(properties.getProperty("forge.major.number")); + list.add(properties.getProperty("forge.minor.number")); + list.add(properties.getProperty("forge.revision.number")); + list.add(properties.getProperty("forge.build.number")); + forgeVersion = String.join(".", list); + } + + if (forgeVersion == null && Files.isRegularFile(fs.getPath("mod_MinecraftForge.info"))) { + try (ZipFileTree tree = new ZipFileTree(new ZipArchiveReader(forgeArchive))) { + LocalModFile metadata = ForgeOldModMetadata.fromFile(null, forgeArchive, tree, "mod_MinecraftForge.info"); + forgeVersion = metadata.getVersion(); + } + } + + if (forgeVersion == null && gameVersion == null && !Files.isRegularFile(fs.getPath("mod_MinecraftForge.class"))) { + return null; + } + + return new ForgeLegacyInstallProfile(gameVersion, forgeVersion); + } catch (IOException ioException) { + LOG.warning("Failed to parse forge archive " + forgeArchive, ioException); + return null; + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java index 005c3e23e90..318e023f948 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java @@ -170,7 +170,7 @@ public void execute() throws IOException { case "modloader-mp" -> ForgeLegacyInstallTask.MODLOADER_MP_DOWNLOAD_URL; default -> null; }; - if (url != null) { + if (url != null && shouldDownloadLibrary(gameRepository, manifest,library, integrityCheck)) { var fileDownloadTask = new FileDownloadTask(url, file, null); fileDownloadTask.setCacheRepository(dependencyManager.getCacheRepository()); fileDownloadTask.setCaching(true); From 1636c6226e0c4e816c034bcd3cfce878df09df6f Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Thu, 3 Sep 2026 22:39:17 +0800 Subject: [PATCH 33/34] update --- .../HMCLLegacyForgeHelper/build.gradle.kts | 25 +++++++++++++++---- .../HMCLModLoaderHelper/build.gradle.kts | 25 +++++++++++++++---- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts b/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts index 27a42576a1c..be3b1061d77 100644 --- a/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts +++ b/minecraft/libraries/HMCLLegacyForgeHelper/build.gradle.kts @@ -1,8 +1,10 @@ +import java.io.RandomAccessFile + version = "1.0" tasks.compileJava { - sourceCompatibility = "1.6" - targetCompatibility = "1.6" + sourceCompatibility = "1.8" + targetCompatibility = "1.8" } tasks.jar { @@ -18,7 +20,20 @@ tasks.jar { } tasks.compileJava { - javaCompiler.set(javaToolchains.compilerFor { - languageVersion.set(JavaLanguageVersion.of(8)) - }) + doLast { + val outputDir = destinationDirectory.get().asFile + outputDir.walkTopDown() + .filter { it.isFile && it.extension == "class" } + .forEach { file -> + RandomAccessFile(file, "rw").use { raf -> + if (raf.length() >= 8) { + val magic = raf.readInt() + if (magic == 0xCAFEBABE.toInt()) { + raf.seek(6) + raf.writeShort(50) + } + } + } + } + } } \ No newline at end of file diff --git a/minecraft/libraries/HMCLModLoaderHelper/build.gradle.kts b/minecraft/libraries/HMCLModLoaderHelper/build.gradle.kts index 0387951b9cd..ed306fba768 100644 --- a/minecraft/libraries/HMCLModLoaderHelper/build.gradle.kts +++ b/minecraft/libraries/HMCLModLoaderHelper/build.gradle.kts @@ -1,8 +1,10 @@ +import java.io.RandomAccessFile + version = "1.0" tasks.compileJava { - sourceCompatibility = "1.6" - targetCompatibility = "1.6" + sourceCompatibility = "1.8" + targetCompatibility = "1.8" } tasks.jar { @@ -18,7 +20,20 @@ tasks.jar { } tasks.compileJava { - javaCompiler.set(javaToolchains.compilerFor { - languageVersion.set(JavaLanguageVersion.of(8)) - }) + doLast { + val outputDir = destinationDirectory.get().asFile + outputDir.walkTopDown() + .filter { it.isFile && it.extension == "class" } + .forEach { file -> + RandomAccessFile(file, "rw").use { raf -> + if (raf.length() >= 8) { + val magic = raf.readInt() + if (magic == 0xCAFEBABE.toInt()) { + raf.seek(6) + raf.writeShort(50) + } + } + } + } + } } \ No newline at end of file From 22850166031aa6eaeb46b0b1bab25fa37c2b75ee Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Sat, 5 Sep 2026 08:51:38 +0800 Subject: [PATCH 34/34] update --- .../hmcl/download/forge/ForgeInstallTask.java | 7 ++++++- .../hmcl/download/forge/ForgeInstallation.java | 13 ++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java index a277513cab9..3ce2cddda72 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -51,6 +51,10 @@ public final class ForgeInstallTask extends Task { private Task dependency; public ForgeInstallTask(DefaultDependencyManager dependencyManager, GameInstanceManifest manifest, ForgeRemoteVersion remoteVersion) { + if (!manifest.isModifiable()) { + throw new IllegalArgumentException("Manifest is not modifiable"); + } + this.dependencyManager = dependencyManager; this.manifest = manifest; this.remote = remoteVersion; @@ -95,7 +99,8 @@ public Collection> getDependencies() { @Override public void execute() throws IOException, VersionMismatchException, UnsupportedInstallationException { - String originalMainClass = dependencyManager.getGameRepository().resolve(manifest).launchManifest().mainClass(); + String originalMainClass = manifest.mainClass(); + if (GameVersionNumber.compare("1.13", remote.getGameVersion()) <= 0) { // Forge 1.13 is not compatible with fabric. if (!GameComponentAnalyzer.FORGE_OPTIFINE_MAIN.contains(originalMainClass)) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java index a42ff59b90a..955c7f60ba5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java @@ -105,14 +105,14 @@ public static Task install(DefaultDependencyManager dependenc return new ForgeLegacyInstallTask(dependencyManager, manifest, forgeProfile != null ? forgeProfile.forgeVersion() : null, installer, type); } case OLD -> { - checkCleanroomCompatibility(dependencyManager, manifest, gameVersion); + checkCleanroomCompatibility(manifest, gameVersion); String installProfileText = Files.readString(fs.getPath("install_profile.json")); ForgeOldInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeOldInstallProfile.class); return new ForgeOldInstallTask(dependencyManager, manifest, modifyVersion(gameVersion, profile.install().path().getVersion().replaceAll("(?i)forge", "")), installer); } case NEW -> { - checkCleanroomCompatibility(dependencyManager, manifest, gameVersion); + checkCleanroomCompatibility(manifest, gameVersion); String installProfileText = Files.readString(fs.getPath("install_profile.json")); ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); @@ -125,12 +125,15 @@ public static Task install(DefaultDependencyManager dependenc /// Rejects Forge installation when the manifest already contains Cleanroom. /// - /// @param dependencyManager repository-scoped download services /// @param manifest working manifest receiving the Forge patch /// @param gameVersion Minecraft version used for component analysis /// @throws UnsupportedInstallationException if the manifest already contains Cleanroom - private static void checkCleanroomCompatibility(DefaultDependencyManager dependencyManager, GameInstanceManifest manifest, String gameVersion) throws UnsupportedInstallationException { - GameComponentAnalyzer analyzer = GameComponentAnalyzer.analyze(dependencyManager.getGameRepository().resolve(manifest), GameVersionNumber.asGameVersion(gameVersion)); + private static void checkCleanroomCompatibility( + GameInstanceManifest manifest, + String gameVersion) throws UnsupportedInstallationException { + GameComponentAnalyzer analyzer = GameComponentAnalyzer.analyze(manifest, GameVersionNumber.asGameVersion(gameVersion)); + + if (analyzer.has(GameComponentType.CLEANROOM)) { throw new UnsupportedInstallationException(CLEANROOM_NOT_COMPATIBLE_WITH_FORGE); }