From f655bbab148e2c1748c72baec51fb957baa9196e Mon Sep 17 00:00:00 2001 From: burningtnt Date: Sat, 25 Jan 2025 10:29:19 +0800 Subject: [PATCH 01/18] Translate exception message. --- .../hmcl/mod/multimc/MultiMCModpackInstallTask.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 973da7726e..155f3353c0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -177,7 +177,13 @@ public void execute() throws Exception { for (Path patchJson : directoryStream) { if (patchJson.toString().endsWith(".json")) { // If json is malformed, we should stop installing this modpack instead of skipping it. - MultiMCInstancePatch multiMCPatch = JsonUtils.GSON.fromJson(FileUtils.readText(patchJson), MultiMCInstancePatch.class); + MultiMCInstancePatch multiMCPatch; + + try { + multiMCPatch = JsonUtils.GSON.fromJson(FileUtils.readText(patchJson), MultiMCInstancePatch.class); + } catch (JsonParseException e) { + throw new IllegalArgumentException("Cannot parse MultiMC patch json: " + patchJson); + } List arguments = new ArrayList<>(); for (String arg : multiMCPatch.getTweakers()) { From 303bdb843f45e547b4881cd6a806f3ef0bb0f804 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Mon, 27 Jan 2025 21:24:53 +0800 Subject: [PATCH 02/18] Fix #3540: MultiMC json-patch native data. --- .../java/org/jackhuang/hmcl/game/Library.java | 39 +++++++++++++++---- .../hmcl/game/tlauncher/TLauncherLibrary.java | 5 ++- .../multimc/MultiMCModpackInstallTask.java | 2 +- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java index 4325eac472..3767dd721d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java @@ -17,11 +17,12 @@ */ package org.jackhuang.hmcl.game; -import com.google.gson.*; +import com.google.gson.JsonParseException; import com.google.gson.annotations.SerializedName; import org.jackhuang.hmcl.util.Constants; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.ToStringBuilder; +import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.TolerableValidationException; import org.jackhuang.hmcl.util.gson.Validation; import org.jackhuang.hmcl.util.platform.Architecture; @@ -46,7 +47,7 @@ public class Library implements Comparable, Validation { private final String url; private final LibrariesDownloadInfo downloads; private final ExtractRules extract; - private final Map natives; + private final Map natives; private final List rules; private final List checksums; @@ -64,7 +65,7 @@ public Library(Artifact artifact, String url, LibrariesDownloadInfo downloads) { this(artifact, url, downloads, null, null, null, null, null, null); } - public Library(Artifact artifact, String url, LibrariesDownloadInfo downloads, List checksums, ExtractRules extract, Map natives, List rules, String hint, String filename) { + public Library(Artifact artifact, String url, LibrariesDownloadInfo downloads, List checksums, ExtractRules extract, Map natives, List rules, String hint, String filename) { this.artifact = artifact; this.url = url; this.downloads = downloads; @@ -93,13 +94,33 @@ public String getVersion() { } public String getClassifier() { - if (artifact.getClassifier() == null) - if (natives != null && natives.containsKey(OperatingSystem.CURRENT_OS)) - return natives.get(OperatingSystem.CURRENT_OS).replace("${arch}", Architecture.SYSTEM_ARCH.getBits().getBit()); - else + if (artifact.getClassifier() == null) { + if (natives == null) { return null; - else + } + + String current = natives.get(OperatingSystem.CURRENT_OS.getCheckedName()); + if (current == null) { + for (String value : natives.values()) { + int i = value.indexOf('-'); + if (i == -1) { + continue; + } + + Architecture architecture = JsonUtils.GSON.fromJson(value.substring(i + 1), Architecture.class); + if (architecture == Architecture.SYSTEM_ARCH) { + current = natives.get(value); + break; + } + } + } + if (current == null) { + return null; + } + return current.replace("${arch}", Architecture.SYSTEM_ARCH.getBits().getBit()); + } else { return artifact.getClassifier(); + } } public ExtractRules getExtract() { @@ -159,6 +180,7 @@ public List getRules() { /** * Hint for how to locate the library file. + * * @return null for default, "local" for location in version/<version>/libraries/filename */ @Nullable @@ -168,6 +190,7 @@ public String getHint() { /** * Available when hint is "local" + * * @return the filename of the local library in version/<version>/libraries/$filename */ @Nullable diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/tlauncher/TLauncherLibrary.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/tlauncher/TLauncherLibrary.java index 665f7ea2b4..41ad87d11b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/tlauncher/TLauncherLibrary.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/tlauncher/TLauncherLibrary.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @Immutable public class TLauncherLibrary { @@ -58,7 +59,9 @@ public Library toLibrary() { new LibrariesDownloadInfo(artifact, classifiers), checksums, extract, - natives, + natives.entrySet().stream().collect(Collectors.toMap( + entry -> entry.getKey().getCheckedName(), Map.Entry::getValue + )), rules, null, null diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 155f3353c0..1cb64e6fbe 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -182,7 +182,7 @@ public void execute() throws Exception { try { multiMCPatch = JsonUtils.GSON.fromJson(FileUtils.readText(patchJson), MultiMCInstancePatch.class); } catch (JsonParseException e) { - throw new IllegalArgumentException("Cannot parse MultiMC patch json: " + patchJson); + throw new IllegalArgumentException("Cannot parse MultiMC patch json: " + patchJson, e); } List arguments = new ArrayList<>(); From 9545b7fc745767d71822480a8a50b1863ee20d4d Mon Sep 17 00:00:00 2001 From: burningtnt Date: Tue, 28 Jan 2025 16:02:19 +0800 Subject: [PATCH 03/18] Fix: Invalid game arguments. --- .../org/jackhuang/hmcl/game/Arguments.java | 4 +++ .../hmcl/game/DefaultGameRepository.java | 13 +++++++--- .../java/org/jackhuang/hmcl/game/Library.java | 4 +++ .../hmcl/game/VersionLibraryBuilder.java | 12 +++++---- .../mod/multimc/MultiMCInstancePatch.java | 12 +++++++-- .../multimc/MultiMCModpackInstallTask.java | 25 +++++++++++++++---- 6 files changed, 54 insertions(+), 16 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java index 7d26e71012..266024f3b0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java @@ -83,6 +83,10 @@ public Arguments addJVMArguments(List jvmArguments) { return new Arguments(getGame(), Lang.merge(getJvm(), list)); } + public Arguments addJVMArgumentsDirect(List list) { + return new Arguments(getGame(), Lang.merge(getJvm(), list)); + } + public static Arguments merge(Arguments a, Arguments b) { if (a == null) return b; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java index 21c63c60ef..cf52be64ef 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java @@ -95,10 +95,15 @@ public File getLibrariesDirectory(Version version) { @Override public File getLibraryFile(Version version, Library lib) { - if ("local".equals(lib.getHint()) && lib.getFileName() != null) - return new File(getVersionRoot(version.getId()), "libraries/" + lib.getFileName()); - else - return new File(getLibrariesDirectory(version), lib.getPath()); + if ("local".equals(lib.getHint())) { + if (lib.getFileName() != null) { + return new File(getVersionRoot(version.getId()), "libraries/" + lib.getFileName()); + } + + return new File(getVersionRoot(version.getId()), "libraries/" + lib.getArtifact().getFileName()); + } + + return new File(getLibrariesDirectory(version), lib.getPath()); } public Path getArtifactFile(Version version, Artifact artifact) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java index 3767dd721d..d893d8112c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java @@ -146,6 +146,10 @@ protected LibraryDownloadInfo getRawDownloadInfo() { } } + public Artifact getArtifact() { + return artifact; + } + public String getPath() { LibraryDownloadInfo temp = getRawDownloadInfo(); if (temp != null && temp.getPath() != null) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionLibraryBuilder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionLibraryBuilder.java index 647183e743..5bbffdc7cc 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionLibraryBuilder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/VersionLibraryBuilder.java @@ -20,10 +20,7 @@ import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.platform.CommandBuilder; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; /** @@ -54,7 +51,12 @@ public Version build() { // The official launcher will not parse the "arguments" property when it detects the presence of "mcArgs". // The "arguments" property with the "rule" is simply ignored here. this.mcArgs.addAll(this.game.stream().map(arg -> arg.toString(new HashMap<>(), new HashMap<>())).flatMap(Collection::stream).collect(Collectors.toList())); - ret = ret.setArguments(null); + + // For compatibility with MultiMC launcher, we only ignore the game arguments but keep the presence of vm arguments. + Arguments arguments = ret.getArguments().orElse(null); + if (arguments != null) { + ret.setArguments(arguments.withGame(Collections.emptyList())); + } // Since $ will be escaped in linux, and our maintain of minecraftArgument will not cause escaping, // so we regenerate the minecraftArgument without escaping. diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java index 7a9f555ebb..84951d4bff 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java @@ -44,6 +44,9 @@ public final class MultiMCInstancePatch { @SerializedName("+tweakers") private final List tweakers; + @SerializedName("+jvmArgs") + private final List jvmArgs; + @SerializedName("+libraries") private final List _libraries; @@ -51,16 +54,17 @@ public final class MultiMCInstancePatch { private final List libraries; public MultiMCInstancePatch() { - this("", "", "", "", "", Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + this("", "", "", "", "", Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); } - public MultiMCInstancePatch(String name, String version, String gameVersion, String mainClass, String fileId, List tweakers, List _libraries, List libraries) { + public MultiMCInstancePatch(String name, String version, String gameVersion, String mainClass, String fileId, List tweakers, List jvmArgs, List _libraries, List libraries) { this.name = name; this.version = version; this.gameVersion = gameVersion; this.mainClass = mainClass; this.fileId = fileId; this.tweakers = new ArrayList<>(tweakers); + this.jvmArgs = new ArrayList<>(jvmArgs); this._libraries = new ArrayList<>(_libraries); this.libraries = new ArrayList<>(libraries); } @@ -89,6 +93,10 @@ public List getTweakers() { return Collections.unmodifiableList(tweakers); } + public List getJvmArgs() { + return jvmArgs; + } + public List getLibraries() { return Lang.merge(_libraries, libraries); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 1cb64e6fbe..13dd51ee11 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -20,6 +20,7 @@ import com.google.gson.JsonParseException; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; +import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.game.Arguments; import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.game.Version; @@ -44,7 +45,6 @@ import java.util.Optional; /** - * * @author huangyuhui */ public final class MultiMCModpackInstallTask extends Task { @@ -141,13 +141,13 @@ public void preExecute() throws Exception { // /.minecraft if (Files.exists(fs.getPath("/.minecraft"))) { subDirectory = "/.minecraft"; - // /minecraft + // /minecraft } else if (Files.exists(fs.getPath("/minecraft"))) { subDirectory = "/minecraft"; - // /[name]/.minecraft + // /[name]/.minecraft } else if (Files.exists(fs.getPath("/" + manifest.getName() + "/.minecraft"))) { subDirectory = "/" + manifest.getName() + "/.minecraft"; - // /[name]/minecraft + // /[name]/minecraft } else if (Files.exists(fs.getPath("/" + manifest.getName() + "/minecraft"))) { subDirectory = "/" + manifest.getName() + "/minecraft"; } else { @@ -191,13 +191,28 @@ public void execute() throws Exception { arguments.add(arg); } - Version patch = new Version(multiMCPatch.getName(), multiMCPatch.getVersion(), 1, new Arguments().addGameArguments(arguments), multiMCPatch.getMainClass(), multiMCPatch.getLibraries()); + Version patch = new Version(multiMCPatch.getName(), multiMCPatch.getVersion(), 1, new Arguments().addGameArguments(arguments).addJVMArguments(multiMCPatch.getJvmArgs()), multiMCPatch.getMainClass(), multiMCPatch.getLibraries()); version = version.addPatch(patch); } } } } + if (version.getMinecraftArguments().isPresent()) { + List patches1 = version.getPatches(); + + for (int i = 0; i < patches1.size(); i++) { + Version patch = patches1.get(i); + if (patch.getId().equals(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId())) { + ArrayList patches2 = new ArrayList<>(patches1); + patches2.set(i, patch.setArguments(new Arguments(null, Arguments.DEFAULT_JVM_ARGUMENTS))); + + version = version.setPatches(patches2); + break; + } + } + } + Path libraries = root.resolve("libraries"); if (Files.exists(libraries)) FileUtils.copyDirectory(libraries, repository.getVersionRoot(name).toPath().resolve("libraries")); From 259fed62e9a3e4e2e15e9f5d26e6a08556ead7b2 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Tue, 28 Jan 2025 19:20:59 +0800 Subject: [PATCH 04/18] Fix: MultiMC json-patch priority --- .../mod/multimc/MultiMCInstancePatch.java | 23 +++++++++++-------- .../multimc/MultiMCModpackInstallTask.java | 3 ++- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java index 84951d4bff..f64a2b6601 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java @@ -21,13 +21,13 @@ import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.Lang; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** - * * @author huangyuhui */ @Immutable @@ -35,6 +35,7 @@ public final class MultiMCInstancePatch { private final String name; private final String version; + private final int order; @SerializedName("mcVersion") private final String gameVersion; @@ -42,24 +43,25 @@ public final class MultiMCInstancePatch { private final String fileId; @SerializedName("+tweakers") + @Nullable private final List tweakers; @SerializedName("+jvmArgs") + @Nullable private final List jvmArgs; @SerializedName("+libraries") + @Nullable private final List _libraries; @SerializedName("libraries") + @Nullable private final List libraries; - public MultiMCInstancePatch() { - this("", "", "", "", "", Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); - } - - public MultiMCInstancePatch(String name, String version, String gameVersion, String mainClass, String fileId, List tweakers, List jvmArgs, List _libraries, List libraries) { + public MultiMCInstancePatch(String name, String version, int order, String gameVersion, String mainClass, String fileId, List tweakers, List jvmArgs, List _libraries, List libraries) { this.name = name; this.version = version; + this.order = order; this.gameVersion = gameVersion; this.mainClass = mainClass; this.fileId = fileId; @@ -77,6 +79,10 @@ public String getVersion() { return version; } + public int getOrder() { + return order; + } + public String getGameVersion() { return gameVersion; } @@ -90,15 +96,14 @@ public String getFileId() { } public List getTweakers() { - return Collections.unmodifiableList(tweakers); + return tweakers != null ? Collections.unmodifiableList(tweakers) : Collections.emptyList(); } public List getJvmArgs() { - return jvmArgs; + return jvmArgs != null ? Collections.unmodifiableList(jvmArgs) : Collections.emptyList(); } public List getLibraries() { return Lang.merge(_libraries, libraries); } - } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 13dd51ee11..ba16a28dac 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -191,7 +191,8 @@ public void execute() throws Exception { arguments.add(arg); } - Version patch = new Version(multiMCPatch.getName(), multiMCPatch.getVersion(), 1, new Arguments().addGameArguments(arguments).addJVMArguments(multiMCPatch.getJvmArgs()), multiMCPatch.getMainClass(), multiMCPatch.getLibraries()); + // 30000: Magic Number. Search 30000 directly in HMCL for more information. + Version patch = new Version(multiMCPatch.getName(), multiMCPatch.getVersion(), multiMCPatch.getOrder() + 30000, new Arguments().addGameArguments(arguments).addJVMArguments(multiMCPatch.getJvmArgs()), multiMCPatch.getMainClass(), multiMCPatch.getLibraries()); version = version.addPatch(patch); } } From c0eb62245721011b667ed9bf9523d677bc11c756 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Wed, 29 Jan 2025 16:44:58 +0800 Subject: [PATCH 05/18] Fix: Read java version from MultiMC Modpack. --- .../download/fabric/FabricInstallTask.java | 2 +- .../download/forge/ForgeNewInstallTask.java | 2 +- .../download/forge/ForgeOldInstallTask.java | 2 +- .../hmcl/download/game/GameInstallTask.java | 2 +- .../neoforge/NeoForgeOldInstallTask.java | 2 +- .../hmcl/download/quilt/QuiltInstallTask.java | 2 +- .../java/org/jackhuang/hmcl/game/Version.java | 9 ++++++ .../mod/multimc/MultiMCInstancePatch.java | 18 ++++++++---- .../multimc/MultiMCModpackInstallTask.java | 28 +++++++++++++++---- 9 files changed, 50 insertions(+), 17 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricInstallTask.java index 0199bdd66e..3938800e2e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricInstallTask.java @@ -119,7 +119,7 @@ private Version getPatch(FabricInfo fabricInfo, String gameVersion, String loade libraries.add(new Library(Artifact.fromDescriptor(fabricInfo.intermediary.maven), "https://maven.fabricmc.net/", null)); libraries.add(new Library(Artifact.fromDescriptor(fabricInfo.loader.maven), "https://maven.fabricmc.net/", null)); - return new Version(LibraryAnalyzer.LibraryType.FABRIC.getPatchId(), loaderVersion, 30000, arguments, mainClass, libraries); + return new Version(LibraryAnalyzer.LibraryType.FABRIC.getPatchId(), loaderVersion, Version.PRIORITY_LOADER, arguments, mainClass, libraries); } public static class FabricInfo { 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 b2b626e0fa..91823b2658 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 @@ -411,7 +411,7 @@ public void execute() throws Exception { dependencyManager.checkLibraryCompletionAsync(forgeVersion, true))); setResult(forgeVersion - .setPriority(30000) + .setPriority(Version.PRIORITY_LOADER) .setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId()) .setVersion(selfVersion)); } 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 a1abeba252..df3b919ad9 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 @@ -82,7 +82,7 @@ public void execute() throws Exception { } setResult(installProfile.getVersionInfo() - .setPriority(30000) + .setPriority(Version.PRIORITY_LOADER) .setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId()) .setVersion(selfVersion)); dependencies.add(dependencyManager.checkLibraryCompletionAsync(installProfile.getVersionInfo(), true)); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameInstallTask.java index 2fd3777c64..aea0e04542 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameInstallTask.java @@ -65,7 +65,7 @@ public boolean isRelyingOnDependencies() { @Override public void execute() throws Exception { Version patch = JsonUtils.fromNonNullJson(downloadTask.getResult(), Version.class) - .setId(MINECRAFT.getPatchId()).setVersion(remote.getGameVersion()).setJar(null).setPriority(0); + .setId(MINECRAFT.getPatchId()).setVersion(remote.getGameVersion()).setJar(null).setPriority(Version.PRIORITY_MC); setResult(patch); Version version = new Version(this.version.getId()).addPatch(patch); 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 3569a6e11f..55ad6f8fae 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 @@ -407,7 +407,7 @@ public void execute() throws Exception { dependencyManager.checkLibraryCompletionAsync(neoForgeVersion, true))); setResult(neoForgeVersion - .setPriority(30000) + .setPriority(Version.PRIORITY_LOADER) .setId(LibraryAnalyzer.LibraryType.NEO_FORGE.getPatchId()) .setVersion(selfVersion)); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltInstallTask.java index 81f3a84f02..c0bd512907 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltInstallTask.java @@ -120,7 +120,7 @@ private Version getPatch(QuiltInfo quiltInfo, String gameVersion, String loaderV libraries.add(new Library(Artifact.fromDescriptor(quiltInfo.intermediary.maven), getMavenRepositoryByGroup(quiltInfo.intermediary.maven), null)); libraries.add(new Library(Artifact.fromDescriptor(quiltInfo.loader.maven), getMavenRepositoryByGroup(quiltInfo.loader.maven), null)); - return new Version(LibraryAnalyzer.LibraryType.QUILT.getPatchId(), loaderVersion, 30000, arguments, mainClass, libraries); + return new Version(LibraryAnalyzer.LibraryType.QUILT.getPatchId(), loaderVersion, Version.PRIORITY_LOADER, arguments, mainClass, libraries); } private static String getMavenRepositoryByGroup(String maven) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java index e7e808be8a..b2716c22b8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java @@ -36,6 +36,11 @@ @Immutable public class Version implements Comparable, Validation { + /** + * Patches with higher priority can override info from other patches, such as mainClass. + */ + public static final int PRIORITY_MC = 0, PRIORITY_LOADER = 30000; + private final String id; private final String version; private final Integer priority; @@ -396,6 +401,10 @@ public Version setMinecraftArguments(String minecraftArguments) { return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches); } + public Version setJavaVersion(GameJavaVersion javaVersion) { + return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches); + } + public Version setArguments(Arguments arguments) { return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java index f64a2b6601..b8655c4f8a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java @@ -42,6 +42,9 @@ public final class MultiMCInstancePatch { private final String mainClass; private final String fileId; + @SerializedName("compatibleJavaMajors") + private final int[] javaMajors; + @SerializedName("+tweakers") @Nullable private final List tweakers; @@ -58,17 +61,18 @@ public final class MultiMCInstancePatch { @Nullable private final List libraries; - public MultiMCInstancePatch(String name, String version, int order, String gameVersion, String mainClass, String fileId, List tweakers, List jvmArgs, List _libraries, List libraries) { + public MultiMCInstancePatch(String name, String version, int order, String gameVersion, String mainClass, String fileId, int[] javaMajors, @Nullable List tweakers, @Nullable List jvmArgs, @Nullable List _libraries, @Nullable List libraries) { this.name = name; this.version = version; this.order = order; this.gameVersion = gameVersion; this.mainClass = mainClass; this.fileId = fileId; - this.tweakers = new ArrayList<>(tweakers); - this.jvmArgs = new ArrayList<>(jvmArgs); - this._libraries = new ArrayList<>(_libraries); - this.libraries = new ArrayList<>(libraries); + this.javaMajors = javaMajors; + this.tweakers = tweakers; + this.jvmArgs = jvmArgs; + this._libraries = _libraries; + this.libraries = libraries; } public String getName() { @@ -83,6 +87,10 @@ public int getOrder() { return order; } + public int[] getJavaMajors() { + return javaMajors; + } + public String getGameVersion() { return gameVersion; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index ba16a28dac..7c32791555 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -23,6 +23,7 @@ import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.game.Arguments; import org.jackhuang.hmcl.game.DefaultGameRepository; +import org.jackhuang.hmcl.game.GameJavaVersion; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.mod.MinecraftInstanceTask; import org.jackhuang.hmcl.mod.Modpack; @@ -39,10 +40,7 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; /** * @author huangyuhui @@ -191,8 +189,26 @@ public void execute() throws Exception { arguments.add(arg); } - // 30000: Magic Number. Search 30000 directly in HMCL for more information. - Version patch = new Version(multiMCPatch.getName(), multiMCPatch.getVersion(), multiMCPatch.getOrder() + 30000, new Arguments().addGameArguments(arguments).addJVMArguments(multiMCPatch.getJvmArgs()), multiMCPatch.getMainClass(), multiMCPatch.getLibraries()); + Version patch = new Version( + multiMCPatch.getName(), multiMCPatch.getVersion(), multiMCPatch.getOrder() + Version.PRIORITY_LOADER, + new Arguments().addGameArguments(arguments).addJVMArguments(multiMCPatch.getJvmArgs()), multiMCPatch.getMainClass(), + multiMCPatch.getLibraries() + ); + + int[] majors = multiMCPatch.getJavaMajors(); + if (majors != null) { + majors = majors.clone(); + Arrays.sort(majors); + + for (int i = majors.length - 1; i >= 0; i--) { + GameJavaVersion jv = GameJavaVersion.get(majors[i]); + if (jv != null) { + patch.setJavaVersion(jv); + break; + } + } + } + version = version.addPatch(patch); } } From 81502674267720c8359f11a7e92e3dfdf8ef7b35 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Wed, 29 Jan 2025 16:49:36 +0800 Subject: [PATCH 06/18] Fix: checkstyle --- .../org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java | 1 - 1 file changed, 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java index b8655c4f8a..70c95b2143 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java @@ -23,7 +23,6 @@ import org.jackhuang.hmcl.util.Lang; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.Collections; import java.util.List; From c86e85d0d7f50a1ece764e434c22a560e752b286 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Sat, 12 Apr 2025 21:26:44 +0800 Subject: [PATCH 07/18] Refactor: 100% (maybe) support for MultiMC modpacks. --- .../org/jackhuang/hmcl/java/JavaManager.java | 17 +++ .../java/org/jackhuang/hmcl/game/Library.java | 78 ++++++++---- .../java/org/jackhuang/hmcl/game/Version.java | 12 +- .../hmcl/mod/multimc/MultiMCComponents.java | 48 +++++++ .../mod/multimc/MultiMCInstancePatch.java | 9 +- .../mod/multimc/MultiMCModpackExportTask.java | 22 ++-- .../multimc/MultiMCModpackInstallTask.java | 119 ++++++++++-------- 7 files changed, 206 insertions(+), 99 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCComponents.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java index ec00801e9f..49b20ab184 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java @@ -285,6 +285,23 @@ public static JavaRuntime findSuitableJava(Collection javaRuntimes, GameJavaVersion suggestedJavaVersion = (version != null && gameVersion != null && gameVersion.compareTo("1.7.10") >= 0) ? version.getJavaVersion() : null; + if (suggestedJavaVersion != null) { + for (JavaRuntime java : javaRuntimes) { + if (forceX86) { + if (!java.getArchitecture().isX86()) + continue; + } else { + if (java.getArchitecture() != Architecture.SYSTEM_ARCH) + continue; + } + + // 100% matched. + if (java.getParsedVersion() == suggestedJavaVersion.getMajorVersion()) { + return java; + } + } + } + JavaRuntime mandatory = null; JavaRuntime suggested = null; for (JavaRuntime java : javaRuntimes) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java index d893d8112c..b6a3a51d33 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java @@ -22,17 +22,13 @@ import org.jackhuang.hmcl.util.Constants; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.ToStringBuilder; -import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.TolerableValidationException; import org.jackhuang.hmcl.util.gson.Validation; import org.jackhuang.hmcl.util.platform.Architecture; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jetbrains.annotations.Nullable; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; /** * A class that describes a Minecraft dependency. @@ -41,6 +37,40 @@ */ @Immutable public class Library implements Comparable, Validation { + /** + *

A possible native descriptors can be: [variant-]os[-key]

+ * + *

+ * Variant can be empty string, 'native', or 'natives'. + * Key can be empty string, system arch, or system arch bit count. + *

+ */ + private static final String[] POSSIBLE_NATIVE_DESCRIPTORS; + + static { + String[] keys = { + "", + Architecture.SYSTEM_ARCH.name().toLowerCase(Locale.ROOT), + Architecture.SYSTEM_ARCH.getBits().getBit() + }, variants = {"", "native", "natives"}; + + POSSIBLE_NATIVE_DESCRIPTORS = new String[keys.length * variants.length]; + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < keys.length; i++) { + for (int j = 0; j < variants.length; j++) { + if (!variants[j].isEmpty()) { + builder.append(variants[j]).append('-'); + } + builder.append(OperatingSystem.CURRENT_OS.getCheckedName()); + if (!keys[i].isEmpty()) { + builder.append('-').append(keys[i]); + } + + POSSIBLE_NATIVE_DESCRIPTORS[i * variants.length + j] = builder.toString(); + builder.setLength(0); + } + } + } @SerializedName("name") private final Artifact artifact; @@ -95,29 +125,23 @@ public String getVersion() { public String getClassifier() { if (artifact.getClassifier() == null) { - if (natives == null) { - return null; - } - - String current = natives.get(OperatingSystem.CURRENT_OS.getCheckedName()); - if (current == null) { - for (String value : natives.values()) { - int i = value.indexOf('-'); - if (i == -1) { - continue; + if (natives != null) { + for (String nativeDescriptor : POSSIBLE_NATIVE_DESCRIPTORS) { + String nd = natives.get(nativeDescriptor); + if (nd != null) { + return nd.replace("${arch}", Architecture.SYSTEM_ARCH.getBits().getBit()); } - - Architecture architecture = JsonUtils.GSON.fromJson(value.substring(i + 1), Architecture.class); - if (architecture == Architecture.SYSTEM_ARCH) { - current = natives.get(value); - break; + } + } else if (downloads != null && downloads.getClassifiers() != null) { + for (String nativeDescriptor : POSSIBLE_NATIVE_DESCRIPTORS) { + LibraryDownloadInfo info = downloads.getClassifiers().get(nativeDescriptor); + if (info != null) { + return nativeDescriptor; } } } - if (current == null) { - return null; - } - return current.replace("${arch}", Architecture.SYSTEM_ARCH.getBits().getBit()); + + return null; } else { return artifact.getClassifier(); } @@ -132,10 +156,12 @@ public boolean appliesToCurrentEnvironment() { } public boolean isNative() { - return natives != null && appliesToCurrentEnvironment(); + return appliesToCurrentEnvironment() && ( + natives != null || (downloads != null && downloads.getClassifiers().keySet().stream().anyMatch(s -> s.startsWith("native"))) + ); } - protected LibraryDownloadInfo getRawDownloadInfo() { + private LibraryDownloadInfo getRawDownloadInfo() { if (downloads != null) { if (isNative()) return downloads.getClassifiers().get(getClassifier()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java index b2716c22b8..ac892ec7e5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java @@ -274,7 +274,7 @@ public Version resolve(VersionProvider provider) throws VersionNotFoundException return resolve(provider, new HashSet<>()).markAsResolved(); } - protected Version merge(Version parent, boolean isPatch) { + public Version merge(Version parent, boolean isPatch, boolean libraryMergeOrder) { return new Version( true, id, @@ -289,7 +289,7 @@ protected Version merge(Version parent, boolean isPatch) { assets == null ? parent.assets : assets, complianceLevel, javaVersion == null ? parent.javaVersion : javaVersion, - Lang.merge(this.libraries, parent.libraries), + libraryMergeOrder ? Lang.merge(parent.libraries, this.libraries) : Lang.merge(this.libraries, parent.libraries), Lang.merge(parent.compatibilityRules, this.compatibilityRules), downloads == null ? parent.downloads : downloads, logging == null ? parent.logging : logging, @@ -319,7 +319,7 @@ protected Version resolve(VersionProvider provider, Set resolvedSoFar) t thisVersion = this.jar == null ? this.setJar(id) : this; } else { // It is supposed to auto install an version in getVersion. - thisVersion = merge(provider.getVersion(inheritsFrom).resolve(provider, resolvedSoFar), false); + thisVersion = merge(provider.getVersion(inheritsFrom).resolve(provider, resolvedSoFar), false, false); } } @@ -332,7 +332,7 @@ protected Version resolve(VersionProvider provider, Set resolvedSoFar) t .sorted(Comparator.comparing(Version::getPriority)) .collect(Collectors.toList()); for (Version patch : sortedPatches) { - thisVersion = patch.setJar(null).merge(thisVersion, true); + thisVersion = patch.setJar(null).merge(thisVersion, true, false); } } @@ -385,6 +385,10 @@ private Version setHidden(Boolean hidden) { return new Version(true, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches); } + public Version setRoot(Boolean root) { + return new Version(true, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches); + } + public Version setId(String id) { return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCComponents.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCComponents.java new file mode 100644 index 0000000000..a8ad5d53ad --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCComponents.java @@ -0,0 +1,48 @@ +package org.jackhuang.hmcl.mod.multimc; + +import org.jackhuang.hmcl.download.LibraryAnalyzer; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public final class MultiMCComponents { + private MultiMCComponents() { + } + + private static final Map ID_TYPE = new HashMap<>(); + + static { + ID_TYPE.put("net.minecraft", LibraryAnalyzer.LibraryType.MINECRAFT); + ID_TYPE.put("net.minecraftforge", LibraryAnalyzer.LibraryType.FORGE); + ID_TYPE.put("net.neoforged", LibraryAnalyzer.LibraryType.NEO_FORGE); + ID_TYPE.put("com.mumfrey.liteloader", LibraryAnalyzer.LibraryType.LITELOADER); + ID_TYPE.put("net.fabricmc.fabric-loader", LibraryAnalyzer.LibraryType.FABRIC); + ID_TYPE.put("org.quiltmc.quilt-loader", LibraryAnalyzer.LibraryType.QUILT); + } + + private static final Map TYPE_ID = + ID_TYPE.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); + + private static final List> PAIRS = new ArrayList<>(ID_TYPE.entrySet()); + + static { + if (TYPE_ID.isEmpty()) { + throw new AssertionError("Please make sure TYPE_ID and PAIRS is initialized after ID_TYPE!"); + } + } + + public static String getComponent(LibraryAnalyzer.LibraryType type) { + return TYPE_ID.get(type); + } + + public static LibraryAnalyzer.LibraryType getComponent(String type) { + return ID_TYPE.get(type); + } + + public static List> getPairs() { + return PAIRS; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java index 70c95b2143..a6c701725a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java @@ -39,8 +39,6 @@ public final class MultiMCInstancePatch { @SerializedName("mcVersion") private final String gameVersion; private final String mainClass; - private final String fileId; - @SerializedName("compatibleJavaMajors") private final int[] javaMajors; @@ -60,13 +58,12 @@ public final class MultiMCInstancePatch { @Nullable private final List libraries; - public MultiMCInstancePatch(String name, String version, int order, String gameVersion, String mainClass, String fileId, int[] javaMajors, @Nullable List tweakers, @Nullable List jvmArgs, @Nullable List _libraries, @Nullable List libraries) { + public MultiMCInstancePatch(String name, String version, int order, String gameVersion, String mainClass, int[] javaMajors, @Nullable List tweakers, @Nullable List jvmArgs, @Nullable List _libraries, @Nullable List libraries) { this.name = name; this.version = version; this.order = order; this.gameVersion = gameVersion; this.mainClass = mainClass; - this.fileId = fileId; this.javaMajors = javaMajors; this.tweakers = tweakers; this.jvmArgs = jvmArgs; @@ -98,10 +95,6 @@ public String getMainClass() { return mainClass; } - public String getFileId() { - return fileId; - } - public List getTweakers() { return tweakers != null ? Collections.unmodifiableList(tweakers) : Collections.emptyList(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackExportTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackExportTask.java index 99ff9ab358..93670a4d56 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackExportTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackExportTask.java @@ -31,6 +31,7 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.List; +import java.util.Map; import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -74,17 +75,16 @@ public void execute() throws Exception { .orElseThrow(() -> new IOException("Cannot parse the version of " + versionId)); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId), gameVersion); List components = new ArrayList<>(); - components.add(new MultiMCManifest.MultiMCManifestComponent(true, false, "net.minecraft", gameVersion)); - analyzer.getVersion(FORGE).ifPresent(forgeVersion -> - components.add(new MultiMCManifest.MultiMCManifestComponent(false, false, "net.minecraftforge", forgeVersion))); - analyzer.getVersion(NEO_FORGE).ifPresent(neoForgeVersion -> - components.add(new MultiMCManifest.MultiMCManifestComponent(false, false, "net.neoforged", neoForgeVersion))); - analyzer.getVersion(LITELOADER).ifPresent(liteLoaderVersion -> - components.add(new MultiMCManifest.MultiMCManifestComponent(false, false, "com.mumfrey.liteloader", liteLoaderVersion))); - analyzer.getVersion(FABRIC).ifPresent(fabricVersion -> - components.add(new MultiMCManifest.MultiMCManifestComponent(false, false, "net.fabricmc.fabric-loader", fabricVersion))); - analyzer.getVersion(QUILT).ifPresent(quiltVersion -> - components.add(new MultiMCManifest.MultiMCManifestComponent(false, false, "org.quiltmc.quilt-loader", quiltVersion))); + components.add(new MultiMCManifest.MultiMCManifestComponent(true, false, MultiMCComponents.getComponent(MINECRAFT), gameVersion)); + + for (Map.Entry pair : MultiMCComponents.getPairs()) { + if (pair.getValue().isModLoader()) { + analyzer.getVersion(pair.getValue()).ifPresent( + v -> components.add(new MultiMCManifest.MultiMCManifestComponent(false, false, pair.getKey(), v)) + ); + } + } + MultiMCManifest mmcPack = new MultiMCManifest(1, components); zip.putTextFile(JsonUtils.GSON.toJson(mmcPack), "mmc-pack.json"); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index ef1b9d4ec8..25ad05cef4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -30,6 +30,7 @@ import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.mod.ModpackInstallTask; import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -69,35 +70,13 @@ public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, Fil GameBuilder builder = dependencyManager.gameBuilder().name(name).gameVersion(manifest.getGameVersion()); if (manifest.getMmcPack() != null) { - Optional forge = manifest.getMmcPack().getComponents().stream().filter(e -> e.getUid().equals("net.minecraftforge")).findAny(); - forge.ifPresent(c -> { - if (c.getVersion() != null) - builder.version("forge", c.getVersion()); - }); - - Optional neoForge = manifest.getMmcPack().getComponents().stream().filter(e -> e.getUid().equals("net.neoforged")).findAny(); - neoForge.ifPresent(c -> { - if (c.getVersion() != null) - builder.version("neoforge", c.getVersion()); - }); - - Optional liteLoader = manifest.getMmcPack().getComponents().stream().filter(e -> e.getUid().equals("com.mumfrey.liteloader")).findAny(); - liteLoader.ifPresent(c -> { - if (c.getVersion() != null) - builder.version("liteloader", c.getVersion()); - }); - - Optional fabric = manifest.getMmcPack().getComponents().stream().filter(e -> e.getUid().equals("net.fabricmc.fabric-loader")).findAny(); - fabric.ifPresent(c -> { - if (c.getVersion() != null) - builder.version("fabric", c.getVersion()); - }); - - Optional quilt = manifest.getMmcPack().getComponents().stream().filter(e -> e.getUid().equals("org.quiltmc.quilt-loader")).findAny(); - quilt.ifPresent(c -> { - if (c.getVersion() != null) - builder.version("quilt", c.getVersion()); - }); + for (MultiMCManifest.MultiMCManifestComponent component : manifest.getMmcPack().getComponents()) { + LibraryAnalyzer.LibraryType type = MultiMCComponents.getComponent(component.getUid()); + String version = component.getVersion(); + if (type != null && version != null) { + builder.version(type.getPatchId(), version); + } + } } dependents.add(builder.buildAsync()); @@ -164,7 +143,24 @@ public List> getDependents() { @Override public void execute() throws Exception { - Version version = repository.readVersionJson(name); + // componentID -> + Map> components = new HashMap<>(); + + for (Version patch : repository.readVersionJson(name).getPatches()) { + LibraryAnalyzer.LibraryType libraryType = LibraryAnalyzer.LibraryType.fromPatchId(patch.getId()); + if (libraryType == null) { + throw new IllegalArgumentException("Unknown library: " + patch.getId()); + } + + String componentID = MultiMCComponents.getComponent(libraryType); + if (componentID == null) { + throw new IllegalArgumentException("Unknown library type: " + libraryType); + } + + if (components.put(componentID, Pair.pair(patch, null)) != null) { + throw new IllegalArgumentException("Duplicate libraries: " + componentID); + } + } try (FileSystem fs = CompressingUtils.readonly(zipFile.toPath()).setAutoDetectEncoding(true).build()) { Path root = MultiMCModpackProvider.getRootPath(fs.getPath("/")); @@ -174,7 +170,7 @@ public void execute() throws Exception { try (DirectoryStream directoryStream = Files.newDirectoryStream(patches)) { for (Path patchJson : directoryStream) { if (patchJson.toString().endsWith(".json")) { - // If json is malformed, we should stop installing this modpack instead of skipping it. + String patchID = FileUtils.getNameWithoutExtension(patchJson); MultiMCInstancePatch multiMCPatch; try { @@ -190,7 +186,7 @@ public void execute() throws Exception { } Version patch = new Version( - multiMCPatch.getName(), multiMCPatch.getVersion(), multiMCPatch.getOrder() + Version.PRIORITY_LOADER, + patchID, multiMCPatch.getVersion(), multiMCPatch.getOrder(), new Arguments().addGameArguments(arguments).addJVMArguments(multiMCPatch.getJvmArgs()), multiMCPatch.getMainClass(), multiMCPatch.getLibraries() ); @@ -203,33 +199,21 @@ public void execute() throws Exception { for (int i = majors.length - 1; i >= 0; i--) { GameJavaVersion jv = GameJavaVersion.get(majors[i]); if (jv != null) { - patch.setJavaVersion(jv); + patch = patch.setJavaVersion(jv); break; } } } - version = version.addPatch(patch); + Pair pair = components.computeIfAbsent(patchID, p -> Pair.pair(null, null)); + if (pair.setValue(patch) != null) { + throw new IllegalArgumentException("Duplicate user patch: " + patchID); + } } } } } - if (version.getMinecraftArguments().isPresent()) { - List patches1 = version.getPatches(); - - for (int i = 0; i < patches1.size(); i++) { - Version patch = patches1.get(i); - if (patch.getId().equals(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId())) { - ArrayList patches2 = new ArrayList<>(patches1); - patches2.set(i, patch.setArguments(new Arguments(null, Arguments.DEFAULT_JVM_ARGUMENTS))); - - version = version.setPatches(patches2); - break; - } - } - } - Path libraries = root.resolve("libraries"); if (Files.exists(libraries)) FileUtils.copyDirectory(libraries, repository.getVersionRoot(name).toPath().resolve("libraries")); @@ -247,6 +231,41 @@ public void execute() throws Exception { } } - dependencies.add(repository.saveAsync(version)); + // If $.minecraftArguments exist, write default VM arguments into $.patches[name=game].arguments.jvm for compatibility. + // See org.jackhuang.hmcl.game.VersionLibraryBuilder::build + + { + Pair pair = components.get(MultiMCComponents.getComponent(LibraryAnalyzer.LibraryType.MINECRAFT)); + + Version mc = pair.getKey(); + if (mc.getMinecraftArguments().isPresent() && mc.getArguments().map(Arguments::getJvm).map(List::isEmpty).orElse(true)) { + pair.setKey(mc.setArguments(new Arguments(null, Arguments.DEFAULT_JVM_ARGUMENTS))); + } + } + + // Rearrange all patches. + + Version artifact = null; + for (MultiMCManifest.MultiMCManifestComponent component : manifest.getMmcPack().getComponents()) { + String componentID = component.getUid(); + + Pair pair = components.get(componentID); + if (pair == null) { + throw new IllegalArgumentException("No such component: " + componentID); + } + + if (pair.getKey() != null) { + artifact = artifact == null ? pair.getKey() : pair.getKey().merge(artifact, true, true); + } + if (pair.getValue() != null) { + artifact = artifact == null ? pair.getValue() : pair.getValue().merge(artifact, true, true); + } + } + + // Erase all patches to reject any modification to MultiMC mod packs for now. + artifact = Objects.requireNonNull(artifact, "There should be at least one component.") + .setPatches(null).setId(name).setJar(name).setRoot(null); + + dependencies.add(repository.saveAsync(artifact)); } } From 399204df1c09bcd0c1413368df28bf22445501b2 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Sun, 13 Apr 2025 14:39:59 +0800 Subject: [PATCH 08/18] Fix: MultiMC Json-Patch $.libraries should override all existing libraries instead of appending. --- .../java/org/jackhuang/hmcl/game/Version.java | 20 +++++++++++++++---- .../multimc/MultiMCModpackInstallTask.java | 17 ++++++++++------ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java index ac892ec7e5..ac00018aca 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java @@ -25,6 +25,7 @@ import java.time.Instant; import java.util.*; +import java.util.function.BiFunction; import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -274,7 +275,18 @@ public Version resolve(VersionProvider provider) throws VersionNotFoundException return resolve(provider, new HashSet<>()).markAsResolved(); } - public Version merge(Version parent, boolean isPatch, boolean libraryMergeOrder) { + /** + *

Custom Library Merge Strategies.

+ * + *

THIS_FIRST: Default implementation. For Version::resolve

+ *

THAT_FIRST, ONLY_THIS: MultiMC implementation. For MultiMCModpackInstallTask

+ */ + public static final BiFunction, List, List> + THIS_FIRST = Lang::merge, + THAT_FIRST = (self, parent) -> Lang.merge(parent, self), + ONLY_THIS = (self, parent) -> self; + + public Version merge(Version parent, boolean isPatch, BiFunction, List, List> libMerge) { return new Version( true, id, @@ -289,7 +301,7 @@ public Version merge(Version parent, boolean isPatch, boolean libraryMergeOrder) assets == null ? parent.assets : assets, complianceLevel, javaVersion == null ? parent.javaVersion : javaVersion, - libraryMergeOrder ? Lang.merge(parent.libraries, this.libraries) : Lang.merge(this.libraries, parent.libraries), + libMerge.apply(this.libraries, parent.libraries), Lang.merge(parent.compatibilityRules, this.compatibilityRules), downloads == null ? parent.downloads : downloads, logging == null ? parent.logging : logging, @@ -319,7 +331,7 @@ protected Version resolve(VersionProvider provider, Set resolvedSoFar) t thisVersion = this.jar == null ? this.setJar(id) : this; } else { // It is supposed to auto install an version in getVersion. - thisVersion = merge(provider.getVersion(inheritsFrom).resolve(provider, resolvedSoFar), false, false); + thisVersion = merge(provider.getVersion(inheritsFrom).resolve(provider, resolvedSoFar), false, THIS_FIRST); } } @@ -332,7 +344,7 @@ protected Version resolve(VersionProvider provider, Set resolvedSoFar) t .sorted(Comparator.comparing(Version::getPriority)) .collect(Collectors.toList()); for (Version patch : sortedPatches) { - thisVersion = patch.setJar(null).merge(thisVersion, true, false); + thisVersion = patch.setJar(null).merge(thisVersion, true, THIS_FIRST); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 25ad05cef4..036affec9b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -254,15 +254,20 @@ public void execute() throws Exception { throw new IllegalArgumentException("No such component: " + componentID); } - if (pair.getKey() != null) { - artifact = artifact == null ? pair.getKey() : pair.getKey().merge(artifact, true, true); - } - if (pair.getValue() != null) { - artifact = artifact == null ? pair.getValue() : pair.getValue().merge(artifact, true, true); + Version original = pair.getKey(), jp = pair.getValue(), tc; + if (original == null) { + tc = Objects.requireNonNull(jp, "Original and Json-Patch should be empty at the same time."); + } else { + if (jp != null) { + original = jp.merge(original, true, Version.ONLY_THIS); + } + tc = original; } + + artifact = artifact == null ? tc : tc.merge(artifact, true, Version.THAT_FIRST); } - // Erase all patches to reject any modification to MultiMC mod packs for now. + // Erase all patches info to reject any modification to MultiMC mod packs. artifact = Objects.requireNonNull(artifact, "There should be at least one component.") .setPatches(null).setId(name).setJar(name).setRoot(null); From 1fe71e3aab91f73378f7c03a81bf51f869477616 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Sun, 13 Apr 2025 14:41:24 +0800 Subject: [PATCH 09/18] Fix: typo --- .../jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 036affec9b..ad3fbb6b20 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -256,7 +256,7 @@ public void execute() throws Exception { Version original = pair.getKey(), jp = pair.getValue(), tc; if (original == null) { - tc = Objects.requireNonNull(jp, "Original and Json-Patch should be empty at the same time."); + tc = Objects.requireNonNull(jp, "Original and Json-Patch shouldn't be empty at the same time."); } else { if (jp != null) { original = jp.merge(original, true, Version.ONLY_THIS); From dda433885704d3602a1f9eda05e74b35a8b92250 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Sun, 13 Apr 2025 16:04:46 +0800 Subject: [PATCH 10/18] Fix: OSRestriction should accept things like 'osx-arm64'. --- .../java/org/jackhuang/hmcl/game/Library.java | 11 ++-- .../jackhuang/hmcl/game/OSRestriction.java | 53 ++++++++++++++++++- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java index b6a3a51d33..386369f67b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java @@ -156,9 +156,14 @@ public boolean appliesToCurrentEnvironment() { } public boolean isNative() { - return appliesToCurrentEnvironment() && ( - natives != null || (downloads != null && downloads.getClassifiers().keySet().stream().anyMatch(s -> s.startsWith("native"))) - ); + if (!appliesToCurrentEnvironment()) { + return false; + } + if (natives != null) { + return true; + } + + return downloads != null && downloads.getClassifiers().keySet().stream().anyMatch(s -> s.startsWith("native")); } private LibraryDownloadInfo getRawDownloadInfo() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/OSRestriction.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/OSRestriction.java index f2a5ae399b..7643ce70ad 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/OSRestriction.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/OSRestriction.java @@ -17,16 +17,22 @@ */ package org.jackhuang.hmcl.game; +import com.google.gson.*; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.platform.Architecture; import org.jackhuang.hmcl.util.platform.OperatingSystem; +import java.io.IOException; import java.util.regex.Pattern; /** - * * @author huangyuhui */ +@JsonAdapter(OSRestriction.JsonAdapterImpl.class) public final class OSRestriction { private final OperatingSystem name; @@ -78,4 +84,49 @@ public boolean allow() { return true; } + public static final class JsonAdapterImpl implements TypeAdapterFactory { + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (type.getRawType() != OSRestriction.class) { + return null; + } + + TypeAdapter thisDelegate = (TypeAdapter) gson.getDelegateAdapter(this, type); + + return (TypeAdapter) new TypeAdapter() { + @Override + public void write(JsonWriter writer, OSRestriction restriction) throws IOException { + thisDelegate.write(writer, restriction); + } + + @Override + public OSRestriction read(JsonReader reader) { + JsonObject element = gson.fromJson(reader, JsonObject.class); + + OSRestriction restriction = thisDelegate.fromJsonTree(element); + if (restriction.getName() != null) { + return restriction; + } + + JsonElement name = element.getAsJsonObject().get("name"); + if (name != null && name.isJsonPrimitive()) { + JsonPrimitive jp = name.getAsJsonPrimitive(); + if (jp.isString()) { + String[] parts = jp.getAsString().split("-", 2); + if (parts.length == 2) { + OperatingSystem os = gson.fromJson(new JsonPrimitive(parts[0]), OperatingSystem.class); + Architecture arch = gson.fromJson(new JsonPrimitive(parts[1]), Architecture.class); + if (os != null && arch != null) { + return new OSRestriction(os, restriction.version, arch.getCheckedName()); + } + } + } + } + + return restriction; + } + }; + } + } } From 31bc7c10e7e3c3ecba25298c234073577d940768 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Sun, 13 Apr 2025 18:19:19 +0800 Subject: [PATCH 11/18] Code cleanup. --- HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java index 266024f3b0..7d26e71012 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Arguments.java @@ -83,10 +83,6 @@ public Arguments addJVMArguments(List jvmArguments) { return new Arguments(getGame(), Lang.merge(getJvm(), list)); } - public Arguments addJVMArgumentsDirect(List list) { - return new Arguments(getGame(), Lang.merge(getJvm(), list)); - } - public static Arguments merge(Arguments a, Arguments b) { if (a == null) return b; From f70b0a3701296ab033e2775a3cee05fefa0ba92d Mon Sep 17 00:00:00 2001 From: burningtnt Date: Mon, 14 Apr 2025 18:56:40 +0800 Subject: [PATCH 12/18] Fix: Deal with JarMods. --- .../java/org/jackhuang/hmcl/game/Library.java | 19 +- .../mod/multimc/MultiMCInstancePatch.java | 10 +- .../multimc/MultiMCModpackInstallTask.java | 170 +++++++++++------- 3 files changed, 130 insertions(+), 69 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java index 386369f67b..976d1e5af2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Library.java @@ -21,6 +21,7 @@ import com.google.gson.annotations.SerializedName; import org.jackhuang.hmcl.util.Constants; import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.ToStringBuilder; import org.jackhuang.hmcl.util.gson.TolerableValidationException; import org.jackhuang.hmcl.util.gson.Validation; @@ -193,12 +194,28 @@ public LibraryDownloadInfo getDownload() { LibraryDownloadInfo temp = getRawDownloadInfo(); String path = getPath(); return new LibraryDownloadInfo(path, - Optional.ofNullable(temp).map(LibraryDownloadInfo::getUrl).orElse(Optional.ofNullable(url).orElse(Constants.DEFAULT_LIBRARY_URL) + path), + computePath(temp, path), temp != null ? temp.getSha1() : null, temp != null ? temp.getSize() : 0 ); } + private String computePath(LibraryDownloadInfo raw, String path) { + if (raw != null) { + String url = raw.getUrl(); + if (url != null) { + return url; + } + } + + String repo = Lang.requireNonNullElse(url, Constants.DEFAULT_LIBRARY_URL); + if (!repo.endsWith("/")) { + repo += '/'; + } + + return repo + path; + } + public boolean hasDownloadURL() { LibraryDownloadInfo temp = getRawDownloadInfo(); if (temp != null) return temp.getUrl() != null; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java index a6c701725a..a21aff8a48 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java @@ -58,7 +58,10 @@ public final class MultiMCInstancePatch { @Nullable private final List libraries; - public MultiMCInstancePatch(String name, String version, int order, String gameVersion, String mainClass, int[] javaMajors, @Nullable List tweakers, @Nullable List jvmArgs, @Nullable List _libraries, @Nullable List libraries) { + @Nullable + private final List jarMods; + + public MultiMCInstancePatch(String name, String version, int order, String gameVersion, String mainClass, int[] javaMajors, @Nullable List tweakers, @Nullable List jvmArgs, @Nullable List _libraries, @Nullable List libraries, @Nullable List jarMods) { this.name = name; this.version = version; this.order = order; @@ -69,6 +72,7 @@ public MultiMCInstancePatch(String name, String version, int order, String gameV this.jvmArgs = jvmArgs; this._libraries = _libraries; this.libraries = libraries; + this.jarMods = jarMods; } public String getName() { @@ -106,4 +110,8 @@ public List getJvmArgs() { public List getLibraries() { return Lang.merge(_libraries, libraries); } + + public List getJarMods() { + return jarMods != null ? Collections.unmodifiableList(jarMods) : Collections.emptyList(); + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index ad3fbb6b20..6c1f9af7d4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -29,11 +29,14 @@ import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.ModpackConfiguration; import org.jackhuang.hmcl.mod.ModpackInstallTask; +import org.jackhuang.hmcl.task.GetTask; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.Pair; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.io.NetworkUtils; import java.io.File; import java.io.IOException; @@ -55,6 +58,7 @@ public final class MultiMCModpackInstallTask extends Task { private final DefaultGameRepository repository; private final List> dependencies = new ArrayList<>(1); private final List> dependents = new ArrayList<>(4); + private final Map componentOriginalPatch = new HashMap<>(); public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, File zipFile, Modpack modpack, MultiMCInstanceConfiguration manifest, String name) { this.zipFile = zipFile; @@ -71,10 +75,21 @@ public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, Fil if (manifest.getMmcPack() != null) { for (MultiMCManifest.MultiMCManifestComponent component : manifest.getMmcPack().getComponents()) { - LibraryAnalyzer.LibraryType type = MultiMCComponents.getComponent(component.getUid()); + String componentID = component.getUid(); String version = component.getVersion(); - if (type != null && version != null) { - builder.version(type.getPatchId(), version); + + if (version != null) { + GetTask task = new GetTask(NetworkUtils.toURL(String.format( + "https://meta.multimc.org/v1/%s/%s.json", componentID, version + ))); + + componentOriginalPatch.put(componentID, task); + dependents.add(task); + + LibraryAnalyzer.LibraryType type = MultiMCComponents.getComponent(componentID); + if (type != null) { + builder.version(type.getPatchId(), version); + } } } } @@ -144,20 +159,15 @@ public List> getDependents() { @Override public void execute() throws Exception { // componentID -> - Map> components = new HashMap<>(); + Map> components = new HashMap<>(); - for (Version patch : repository.readVersionJson(name).getPatches()) { - LibraryAnalyzer.LibraryType libraryType = LibraryAnalyzer.LibraryType.fromPatchId(patch.getId()); - if (libraryType == null) { - throw new IllegalArgumentException("Unknown library: " + patch.getId()); - } + for (Map.Entry entry : componentOriginalPatch.entrySet()) { + String componentID = entry.getKey(); + String patchJson = Objects.requireNonNull(entry.getValue().getResult()); - String componentID = MultiMCComponents.getComponent(libraryType); - if (componentID == null) { - throw new IllegalArgumentException("Unknown library type: " + libraryType); - } - - if (components.put(componentID, Pair.pair(patch, null)) != null) { + if (components.put(componentID, Pair.pair(convertPatchToVersion( + readPatch(patchJson), componentID), null + )) != null) { throw new IllegalArgumentException("Duplicate libraries: " + componentID); } } @@ -171,42 +181,9 @@ public void execute() throws Exception { for (Path patchJson : directoryStream) { if (patchJson.toString().endsWith(".json")) { String patchID = FileUtils.getNameWithoutExtension(patchJson); - MultiMCInstancePatch multiMCPatch; - - try { - multiMCPatch = JsonUtils.GSON.fromJson(FileUtils.readText(patchJson), MultiMCInstancePatch.class); - } catch (JsonParseException e) { - throw new IllegalArgumentException("Cannot parse MultiMC patch json: " + patchJson, e); - } - List arguments = new ArrayList<>(); - for (String arg : multiMCPatch.getTweakers()) { - arguments.add("--tweakClass"); - arguments.add(arg); - } - - Version patch = new Version( - patchID, multiMCPatch.getVersion(), multiMCPatch.getOrder(), - new Arguments().addGameArguments(arguments).addJVMArguments(multiMCPatch.getJvmArgs()), multiMCPatch.getMainClass(), - multiMCPatch.getLibraries() - ); - - int[] majors = multiMCPatch.getJavaMajors(); - if (majors != null) { - majors = majors.clone(); - Arrays.sort(majors); - - for (int i = majors.length - 1; i >= 0; i--) { - GameJavaVersion jv = GameJavaVersion.get(majors[i]); - if (jv != null) { - patch = patch.setJavaVersion(jv); - break; - } - } - } - - Pair pair = components.computeIfAbsent(patchID, p -> Pair.pair(null, null)); - if (pair.setValue(patch) != null) { + Pair pair = components.computeIfAbsent(patchID, p -> Pair.pair(null, null)); + if (pair.setValue(readPatch(FileUtils.readText(patchJson))) != null) { throw new IllegalArgumentException("Duplicate user patch: " + patchID); } } @@ -235,10 +212,10 @@ public void execute() throws Exception { // See org.jackhuang.hmcl.game.VersionLibraryBuilder::build { - Pair pair = components.get(MultiMCComponents.getComponent(LibraryAnalyzer.LibraryType.MINECRAFT)); + Pair pair = components.get(MultiMCComponents.getComponent(LibraryAnalyzer.LibraryType.MINECRAFT)); Version mc = pair.getKey(); - if (mc.getMinecraftArguments().isPresent() && mc.getArguments().map(Arguments::getJvm).map(List::isEmpty).orElse(true)) { + if (mc.getArguments().map(Arguments::getJvm).map(List::isEmpty).orElse(true)) { pair.setKey(mc.setArguments(new Arguments(null, Arguments.DEFAULT_JVM_ARGUMENTS))); } } @@ -246,25 +223,44 @@ public void execute() throws Exception { // Rearrange all patches. Version artifact = null; - for (MultiMCManifest.MultiMCManifestComponent component : manifest.getMmcPack().getComponents()) { - String componentID = component.getUid(); + try (FileSystem mc = CompressingUtils.writable( + repository.getVersionRoot(name).toPath().resolve(name + ".jar") + ).setAutoDetectEncoding(true).build()) { + for (MultiMCManifest.MultiMCManifestComponent component : manifest.getMmcPack().getComponents()) { + String componentID = component.getUid(); - Pair pair = components.get(componentID); - if (pair == null) { - throw new IllegalArgumentException("No such component: " + componentID); - } + Pair pair = components.get(componentID); + if (pair == null) { + throw new IllegalArgumentException("No such component: " + componentID); + } - Version original = pair.getKey(), jp = pair.getValue(), tc; - if (original == null) { - tc = Objects.requireNonNull(jp, "Original and Json-Patch shouldn't be empty at the same time."); - } else { - if (jp != null) { - original = jp.merge(original, true, Version.ONLY_THIS); + Version original = pair.getKey(); + MultiMCInstancePatch jp = pair.getValue(); + if (jp != null && !jp.getJarMods().isEmpty()) { + // JarMod. Merge it into minecraft.jar + if (original != null || !componentID.startsWith("org.multimc.jarmod.")) { + throw new IllegalArgumentException("Illegal jar mod: " + componentID); + } + + try (FileSystem jm = CompressingUtils.readonly(repository.getVersionRoot(name).toPath().resolve( + "jarmods/" + StringUtils.removePrefix(componentID, "org.multimc.jarmod.") + ".jar" + )).setAutoDetectEncoding(true).build()) { + FileUtils.copyDirectory(jm.getPath("/"), mc.getPath("/")); + } + } else { + Version tc, pp = jp == null ? null : convertPatchToVersion(jp, componentID); + + if (original == null) { + tc = Objects.requireNonNull(pp, "Original and Json-Patch shouldn't be empty at the same time."); + } else if (jp != null) { + tc = pp.merge(original, true, Version.ONLY_THIS); + } else { + tc = original; + } + + artifact = artifact == null ? tc : tc.merge(artifact, true, Version.THAT_FIRST); } - tc = original; } - - artifact = artifact == null ? tc : tc.merge(artifact, true, Version.THAT_FIRST); } // Erase all patches info to reject any modification to MultiMC mod packs. @@ -273,4 +269,44 @@ public void execute() throws Exception { dependencies.add(repository.saveAsync(artifact)); } + + private MultiMCInstancePatch readPatch(String patchJson) { + MultiMCInstancePatch patch; + try { + patch = JsonUtils.GSON.fromJson(patchJson, MultiMCInstancePatch.class); + } catch (JsonParseException e) { + throw new IllegalArgumentException("Cannot parse MultiMC patch json: " + patchJson, e); + } + return patch; + } + + private Version convertPatchToVersion(MultiMCInstancePatch patch, String patchID) { + List arguments = new ArrayList<>(); + for (String arg : patch.getTweakers()) { + arguments.add("--tweakClass"); + arguments.add(arg); + } + + Version version = new Version( + patchID, patch.getVersion(), patch.getOrder(), + new Arguments().addGameArguments(arguments).addJVMArguments(patch.getJvmArgs()), patch.getMainClass(), + patch.getLibraries() + ); + + int[] majors = patch.getJavaMajors(); + if (majors != null) { + majors = majors.clone(); + Arrays.sort(majors); + + for (int i = majors.length - 1; i >= 0; i--) { + GameJavaVersion jv = GameJavaVersion.get(majors[i]); + if (jv != null) { + version = version.setJavaVersion(jv); + break; + } + } + } + + return version; + } } From 511bc8caaab89220023a355198b9122336a86432 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Fri, 18 Apr 2025 22:16:45 +0800 Subject: [PATCH 13/18] Fix: Json-Patch should accept $.minecraftArguments. Copy MultiMC sucking hardcoded values :) --- .../mod/multimc/MultiMCInstancePatch.java | 9 +++++++- .../multimc/MultiMCModpackInstallTask.java | 22 ++++++++++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java index a21aff8a48..ea21434ca3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java @@ -36,6 +36,8 @@ public final class MultiMCInstancePatch { private final String version; private final int order; + private final String minecraftArguments; + @SerializedName("mcVersion") private final String gameVersion; private final String mainClass; @@ -61,10 +63,11 @@ public final class MultiMCInstancePatch { @Nullable private final List jarMods; - public MultiMCInstancePatch(String name, String version, int order, String gameVersion, String mainClass, int[] javaMajors, @Nullable List tweakers, @Nullable List jvmArgs, @Nullable List _libraries, @Nullable List libraries, @Nullable List jarMods) { + public MultiMCInstancePatch(String name, String version, int order, String minecraftArguments, String gameVersion, String mainClass, int[] javaMajors, @Nullable List tweakers, @Nullable List jvmArgs, @Nullable List _libraries, @Nullable List libraries, @Nullable List jarMods) { this.name = name; this.version = version; this.order = order; + this.minecraftArguments = minecraftArguments; this.gameVersion = gameVersion; this.mainClass = mainClass; this.javaMajors = javaMajors; @@ -83,6 +86,10 @@ public String getVersion() { return version; } + public String getMinecraftArguments() { + return minecraftArguments; + } + public int getOrder() { return order; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 6c1f9af7d4..9378cb4297 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -78,6 +78,17 @@ public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, Fil String componentID = component.getUid(); String version = component.getVersion(); + // https://github.com/MultiMC/Launcher/blob/develop/launcher/minecraft/ComponentUpdateTask.cpp#L586-L593 + switch (componentID) { + case "org.lwjgl": { + version = "2.9.1"; + break; + } + case "org.lwjgl3": { + version = "3.1.2"; + } + } + if (version != null) { GetTask task = new GetTask(NetworkUtils.toURL(String.format( "https://meta.multimc.org/v1/%s/%s.json", componentID, version @@ -287,11 +298,12 @@ private Version convertPatchToVersion(MultiMCInstancePatch patch, String patchID arguments.add(arg); } - Version version = new Version( - patchID, patch.getVersion(), patch.getOrder(), - new Arguments().addGameArguments(arguments).addJVMArguments(patch.getJvmArgs()), patch.getMainClass(), - patch.getLibraries() - ); + Version version = new Version(patchID) + .setVersion(patch.getVersion()) + .setArguments(new Arguments().addGameArguments(arguments).addJVMArguments(patch.getJvmArgs())) + .setMainClass(patch.getMainClass()) + .setMinecraftArguments(patch.getMinecraftArguments()) + .setLibraries(patch.getLibraries()); int[] majors = patch.getJavaMajors(); if (majors != null) { From cf54161b7129d3dc5aa24385b696a10b0defc6c5 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Sat, 19 Apr 2025 11:02:51 +0800 Subject: [PATCH 14/18] More hardcoded versions :( --- .../multimc/MultiMCModpackInstallTask.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 9378cb4297..6298f15226 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -76,9 +76,10 @@ public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, Fil if (manifest.getMmcPack() != null) { for (MultiMCManifest.MultiMCManifestComponent component : manifest.getMmcPack().getComponents()) { String componentID = component.getUid(); - String version = component.getVersion(); - // https://github.com/MultiMC/Launcher/blob/develop/launcher/minecraft/ComponentUpdateTask.cpp#L586-L593 + String version; + // https://github.com/MultiMC/Launcher/blob/develop/launcher/minecraft/ComponentUpdateTask.cpp#L586-L602 + labelSwitch: switch (componentID) { case "org.lwjgl": { version = "2.9.1"; @@ -86,6 +87,22 @@ public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, Fil } case "org.lwjgl3": { version = "3.1.2"; + break; + } + case "net.fabricmc.intermediary": + case "org.quiltmc.hashed": { + for (MultiMCManifest.MultiMCManifestComponent c : manifest.getMmcPack().getComponents()) { + if (MultiMCComponents.getComponent(c.getUid()) == LibraryAnalyzer.LibraryType.MINECRAFT) { + version = Objects.requireNonNull(c.getVersion(), "Version of Minecraft must be specific."); + break labelSwitch; + } + } + + version = null; + break; + } + default: { + version = component.getVersion(); } } @@ -305,6 +322,9 @@ private Version convertPatchToVersion(MultiMCInstancePatch patch, String patchID .setMinecraftArguments(patch.getMinecraftArguments()) .setLibraries(patch.getLibraries()); + // Workaround: Official Version Json can only store one GameJavaVersion, not a array of all suitable java versions. + // For compatibility with official launcher and any other launchers, + // a transform is made between int[] and GameJavaVersion. int[] majors = patch.getJavaMajors(); if (majors != null) { majors = majors.clone(); From 5defa46e1a5b20ab441451d3870216c393c4d22c Mon Sep 17 00:00:00 2001 From: burningtnt Date: Sat, 19 Apr 2025 14:34:17 +0800 Subject: [PATCH 15/18] Fix: Hardcoded versioning is only used for vacant version info. Support downloading meta from PrismLauncher. --- .../hmcl/mod/multimc/MultiMCComponents.java | 14 +++-- .../multimc/MultiMCModpackInstallTask.java | 55 ++++++++++--------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCComponents.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCComponents.java index a8ad5d53ad..1112a607fb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCComponents.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCComponents.java @@ -2,13 +2,15 @@ import org.jackhuang.hmcl.download.LibraryAnalyzer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; public final class MultiMCComponents { + public static final String[] META = { + "https://meta.multimc.org/v1/%s/%s.json", + "https://meta.prismlauncher.org/v1/%s/%s.json", + }; + private MultiMCComponents() { } @@ -26,7 +28,7 @@ private MultiMCComponents() { private static final Map TYPE_ID = ID_TYPE.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); - private static final List> PAIRS = new ArrayList<>(ID_TYPE.entrySet()); + private static final Collection> PAIRS = Collections.unmodifiableCollection(ID_TYPE.entrySet()); static { if (TYPE_ID.isEmpty()) { @@ -42,7 +44,7 @@ public static LibraryAnalyzer.LibraryType getComponent(String type) { return ID_TYPE.get(type); } - public static List> getPairs() { + public static Collection> getPairs() { return PAIRS; } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 6298f15226..9580d025c2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -40,6 +40,7 @@ import java.io.File; import java.io.IOException; +import java.net.URL; import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; import java.nio.file.Files; @@ -77,39 +78,39 @@ public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, Fil for (MultiMCManifest.MultiMCManifestComponent component : manifest.getMmcPack().getComponents()) { String componentID = component.getUid(); - String version; - // https://github.com/MultiMC/Launcher/blob/develop/launcher/minecraft/ComponentUpdateTask.cpp#L586-L602 - labelSwitch: - switch (componentID) { - case "org.lwjgl": { - version = "2.9.1"; - break; - } - case "org.lwjgl3": { - version = "3.1.2"; - break; - } - case "net.fabricmc.intermediary": - case "org.quiltmc.hashed": { - for (MultiMCManifest.MultiMCManifestComponent c : manifest.getMmcPack().getComponents()) { - if (MultiMCComponents.getComponent(c.getUid()) == LibraryAnalyzer.LibraryType.MINECRAFT) { - version = Objects.requireNonNull(c.getVersion(), "Version of Minecraft must be specific."); - break labelSwitch; + String version = component.getVersion(); + if (version == null) { + // https://github.com/MultiMC/Launcher/blob/develop/launcher/minecraft/ComponentUpdateTask.cpp#L586-L602 + labelSwitch: + switch (componentID) { + case "org.lwjgl": { + version = "2.9.1"; + break; + } + case "org.lwjgl3": { + version = "3.1.2"; + break; + } + case "net.fabricmc.intermediary": + case "org.quiltmc.hashed": { + for (MultiMCManifest.MultiMCManifestComponent c : manifest.getMmcPack().getComponents()) { + if (MultiMCComponents.getComponent(c.getUid()) == LibraryAnalyzer.LibraryType.MINECRAFT) { + version = Objects.requireNonNull(c.getVersion(), "Version of Minecraft must be specific."); + break labelSwitch; + } } + break; } - - version = null; - break; - } - default: { - version = component.getVersion(); } } if (version != null) { - GetTask task = new GetTask(NetworkUtils.toURL(String.format( - "https://meta.multimc.org/v1/%s/%s.json", componentID, version - ))); + List urls = new ArrayList<>(MultiMCComponents.META.length); + for (String s : MultiMCComponents.META) { + urls.add(NetworkUtils.toURL(String.format(s, componentID, version))); + } + + GetTask task = new GetTask(urls); componentOriginalPatch.put(componentID, task); dependents.add(task); From c9b0a8f08b4852139c209acc7c6860164bb45579 Mon Sep 17 00:00:00 2001 From: burningtnt Date: Thu, 24 Apr 2025 22:28:37 +0800 Subject: [PATCH 16/18] Fix: Support assetIndex. --- .../java/org/jackhuang/hmcl/game/Version.java | 4 ++ .../mod/multimc/MultiMCInstancePatch.java | 46 ++++++++++++++++++- .../multimc/MultiMCModpackInstallTask.java | 41 ++--------------- 3 files changed, 51 insertions(+), 40 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java index ac00018aca..3d309a3847 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java @@ -437,6 +437,10 @@ public Version setJar(String jar) { return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches); } + public Version setAssetIndex(AssetIndexInfo assetIndex) { + return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches); + } + public Version setLibraries(List libraries) { return new Version(resolved, id, version, priority, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, complianceLevel, javaVersion, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion, hidden, root, patches); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java index ea21434ca3..579c6d69d2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCInstancePatch.java @@ -18,11 +18,13 @@ package org.jackhuang.hmcl.mod.multimc; import com.google.gson.annotations.SerializedName; -import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.Lang; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -35,6 +37,7 @@ public final class MultiMCInstancePatch { private final String name; private final String version; private final int order; + private final AssetIndexInfo assetIndex; private final String minecraftArguments; @@ -63,10 +66,11 @@ public final class MultiMCInstancePatch { @Nullable private final List jarMods; - public MultiMCInstancePatch(String name, String version, int order, String minecraftArguments, String gameVersion, String mainClass, int[] javaMajors, @Nullable List tweakers, @Nullable List jvmArgs, @Nullable List _libraries, @Nullable List libraries, @Nullable List jarMods) { + public MultiMCInstancePatch(String name, String version, int order, AssetIndexInfo assetIndex, String minecraftArguments, String gameVersion, String mainClass, int[] javaMajors, @Nullable List tweakers, @Nullable List jvmArgs, @Nullable List _libraries, @Nullable List libraries, @Nullable List jarMods) { this.name = name; this.version = version; this.order = order; + this.assetIndex = assetIndex; this.minecraftArguments = minecraftArguments; this.gameVersion = gameVersion; this.mainClass = mainClass; @@ -86,6 +90,10 @@ public String getVersion() { return version; } + public AssetIndexInfo getAssetIndex() { + return assetIndex; + } + public String getMinecraftArguments() { return minecraftArguments; } @@ -121,4 +129,38 @@ public List getLibraries() { public List getJarMods() { return jarMods != null ? Collections.unmodifiableList(jarMods) : Collections.emptyList(); } + + public Version asVersion(String patchID) { + List arguments = new ArrayList<>(); + for (String arg : getTweakers()) { + arguments.add("--tweakClass"); + arguments.add(arg); + } + + Version version = new Version(patchID) + .setVersion(getVersion()) + .setArguments(new Arguments().addGameArguments(arguments).addJVMArguments(getJvmArgs())) + .setMainClass(getMainClass()) + .setMinecraftArguments(getMinecraftArguments()) + .setLibraries(getLibraries()) + .setAssetIndex(getAssetIndex()); + + /* TODO: Official Version Json can only store one GameJavaVersion, not a array of all suitable java versions. + For compatibility with official launcher and any other launchers, a transform is made between int[] and GameJavaVersion. */ + int[] majors = getJavaMajors(); + if (majors != null) { + majors = majors.clone(); + Arrays.sort(majors); + + for (int i = majors.length - 1; i >= 0; i--) { + GameJavaVersion jv = GameJavaVersion.get(majors[i]); + if (jv != null) { + version = version.setJavaVersion(jv); + break; + } + } + } + + return version; + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 9580d025c2..fcb1787084 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -23,7 +23,6 @@ import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.game.Arguments; import org.jackhuang.hmcl.game.DefaultGameRepository; -import org.jackhuang.hmcl.game.GameJavaVersion; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.mod.MinecraftInstanceTask; import org.jackhuang.hmcl.mod.Modpack; @@ -194,8 +193,8 @@ public void execute() throws Exception { String componentID = entry.getKey(); String patchJson = Objects.requireNonNull(entry.getValue().getResult()); - if (components.put(componentID, Pair.pair(convertPatchToVersion( - readPatch(patchJson), componentID), null + if (components.put(componentID, Pair.pair(readPatch(patchJson).asVersion( + componentID), null )) != null) { throw new IllegalArgumentException("Duplicate libraries: " + componentID); } @@ -277,7 +276,7 @@ public void execute() throws Exception { FileUtils.copyDirectory(jm.getPath("/"), mc.getPath("/")); } } else { - Version tc, pp = jp == null ? null : convertPatchToVersion(jp, componentID); + Version tc, pp = jp == null ? null : jp.asVersion(componentID); if (original == null) { tc = Objects.requireNonNull(pp, "Original and Json-Patch shouldn't be empty at the same time."); @@ -308,38 +307,4 @@ private MultiMCInstancePatch readPatch(String patchJson) { } return patch; } - - private Version convertPatchToVersion(MultiMCInstancePatch patch, String patchID) { - List arguments = new ArrayList<>(); - for (String arg : patch.getTweakers()) { - arguments.add("--tweakClass"); - arguments.add(arg); - } - - Version version = new Version(patchID) - .setVersion(patch.getVersion()) - .setArguments(new Arguments().addGameArguments(arguments).addJVMArguments(patch.getJvmArgs())) - .setMainClass(patch.getMainClass()) - .setMinecraftArguments(patch.getMinecraftArguments()) - .setLibraries(patch.getLibraries()); - - // Workaround: Official Version Json can only store one GameJavaVersion, not a array of all suitable java versions. - // For compatibility with official launcher and any other launchers, - // a transform is made between int[] and GameJavaVersion. - int[] majors = patch.getJavaMajors(); - if (majors != null) { - majors = majors.clone(); - Arrays.sort(majors); - - for (int i = majors.length - 1; i >= 0; i--) { - GameJavaVersion jv = GameJavaVersion.get(majors[i]); - if (jv != null) { - version = version.setJavaVersion(jv); - break; - } - } - } - - return version; - } } From 60cba551ece4b19b2328f2f7d75c37483f871dac Mon Sep 17 00:00:00 2001 From: burningtnt Date: Fri, 25 Apr 2025 20:49:33 +0800 Subject: [PATCH 17/18] Code cleanup. --- .../jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index fcb1787084..511ab724b1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -299,12 +299,10 @@ public void execute() throws Exception { } private MultiMCInstancePatch readPatch(String patchJson) { - MultiMCInstancePatch patch; try { - patch = JsonUtils.GSON.fromJson(patchJson, MultiMCInstancePatch.class); + return JsonUtils.GSON.fromJson(patchJson, MultiMCInstancePatch.class); } catch (JsonParseException e) { throw new IllegalArgumentException("Cannot parse MultiMC patch json: " + patchJson, e); } - return patch; } } From f488adb1c74fcb8fc4ce570e0201ea2c3c1a737b Mon Sep 17 00:00:00 2001 From: burningtnt Date: Sun, 11 May 2025 14:58:23 +0800 Subject: [PATCH 18/18] Code cleanup. --- .../jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java index 511ab724b1..998e2bc2aa 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCModpackInstallTask.java @@ -80,7 +80,6 @@ public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, Fil String version = component.getVersion(); if (version == null) { // https://github.com/MultiMC/Launcher/blob/develop/launcher/minecraft/ComponentUpdateTask.cpp#L586-L602 - labelSwitch: switch (componentID) { case "org.lwjgl": { version = "2.9.1"; @@ -95,7 +94,7 @@ public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, Fil for (MultiMCManifest.MultiMCManifestComponent c : manifest.getMmcPack().getComponents()) { if (MultiMCComponents.getComponent(c.getUid()) == LibraryAnalyzer.LibraryType.MINECRAFT) { version = Objects.requireNonNull(c.getVersion(), "Version of Minecraft must be specific."); - break labelSwitch; + break; } } break;