Fix: 无法安装带有 Fabric 的非标准 MMC 整合包 (#4034)
- 修复无法安装带有 Fabric 的非标准 MMC 整合包的漏洞 - 在安装时写入当前安装器实现信息和启动器信息,以在后续调试时获得更多信息 Fix #4049
This commit is contained in:
@@ -32,3 +32,16 @@ dependencies {
|
||||
testImplementation(libs.jna.platform)
|
||||
testImplementation(libs.jimfs)
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
listOf(
|
||||
"HMCLTransformerDiscoveryService",
|
||||
"HMCLMultiMCBootstrap"
|
||||
).map { project(":$it").tasks["jar"] as Jar }.forEach { task ->
|
||||
dependsOn(task)
|
||||
|
||||
into("assets/game") {
|
||||
from(task.outputs.files)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +241,10 @@ public class Library implements Comparable<Library>, Validation {
|
||||
return hint;
|
||||
}
|
||||
|
||||
public Library withoutCommunityFields() {
|
||||
return new Library(artifact, url, downloads, checksums, extract, natives, rules, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Available when hint is "local"
|
||||
*
|
||||
|
||||
@@ -4,7 +4,9 @@ import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class MultiMCComponents {
|
||||
@@ -12,6 +14,41 @@ public final class MultiMCComponents {
|
||||
private MultiMCComponents() {
|
||||
}
|
||||
|
||||
private static final Map<String, String> INSTALLER_PROFILE = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
// Please append a phrase below while fixing bugs or implementing new features for Instance Format transformer
|
||||
INSTALLER_PROFILE.put("Patches", "recursive install, fabric & quilt intermediary");
|
||||
|
||||
// Check whether MultiMCComponents is 'org.jackhuang.hmcl.mod.multimc.MultiMCComponents'.
|
||||
// We use a base64-encoded value here to prevent string literals from being replaced by IDE if users trigger the 'Refactor' feature.
|
||||
if (new String(
|
||||
Base64.getDecoder().decode("b3JnLmphY2todWFuZy5obWNsLm1vZC5tdWx0aW1jLk11bHRpTUNDb21wb25lbnRz"),
|
||||
StandardCharsets.UTF_8
|
||||
).equals(MultiMCComponents.class.getName())) {
|
||||
INSTALLER_PROFILE.put("Implementation", "Probably vanilla. Class location is not modified (org.jackhuang.hmcl.mod.multimc.MultiMCComponents).");
|
||||
} else {
|
||||
INSTALLER_PROFILE.put("Implementation", "Not vanilla. Class location is " + MultiMCComponents.class.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public static void setImplementation(String implementation) {
|
||||
INSTALLER_PROFILE.put("Implementation", implementation);
|
||||
}
|
||||
|
||||
public static String getInstallerProfile() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Map.Entry<String, String> entry : INSTALLER_PROFILE.entrySet()) {
|
||||
builder.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
|
||||
}
|
||||
|
||||
if (builder.length() != 0) {
|
||||
builder.setLength(builder.length() - 1);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static final Map<String, LibraryAnalyzer.LibraryType> ID_TYPE = new HashMap<>();
|
||||
|
||||
static {
|
||||
@@ -46,7 +83,25 @@ public final class MultiMCComponents {
|
||||
return PAIRS;
|
||||
}
|
||||
|
||||
public static URI getMetaURL(String componentID, String version) {
|
||||
public static URI getMetaURL(String componentID, String version, String mcVersion) {
|
||||
if (version == null) {
|
||||
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": {
|
||||
version = mcVersion;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NetworkUtils.toURI(String.format("https://meta.multimc.org/v1/%s/%s.json", componentID, version));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonMap;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.logging.Logger;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
@@ -45,6 +46,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -54,6 +56,8 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@Immutable
|
||||
public final class MultiMCInstancePatch {
|
||||
public static final Library BOOTSTRAP_LIBRARY = new Library(new Artifact("org.jackhuang.hmcl", "mmc-bootstrap", "1.0"));
|
||||
|
||||
private final int formatVersion;
|
||||
|
||||
@SerializedName("uid")
|
||||
@@ -390,6 +394,15 @@ public final class MultiMCInstancePatch {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
libraries.add(0, BOOTSTRAP_LIBRARY);
|
||||
jvmArguments.add(new StringArgument("-Dhmcl.mmc.bootstrap=" + NetworkUtils.withQuery("hmcl:///bootstrap_profile_v1/", Map.of(
|
||||
"main_class", mainClass,
|
||||
"installer", MultiMCComponents.getInstallerProfile()
|
||||
))));
|
||||
mainClass = "org.jackhuang.hmcl.HMCLMultiMCBootstrap";
|
||||
}
|
||||
|
||||
Version version = new Version(versionID)
|
||||
.setArguments(new Arguments().addGameArguments(minecraftArguments).addJVMArgumentsDirect(jvmArguments))
|
||||
.setMainClass(mainClass)
|
||||
|
||||
@@ -19,11 +19,14 @@ package org.jackhuang.hmcl.mod.multimc;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.game.Artifact;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
@@ -38,14 +41,16 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -54,16 +59,20 @@ import java.util.Objects;
|
||||
* <p>A task transforming MultiMC Modpack Scheme to Official Launcher Scheme.
|
||||
* The transforming process contains 7 stage:
|
||||
* <ul>
|
||||
* <li>General Setup</li>
|
||||
* <li>Load Components</li>
|
||||
* <li>Resolve Json-Patch</li>
|
||||
* <li>Build Artifact</li>
|
||||
* <li>Copy Embedded Files</li>
|
||||
* <li>Assemble Game</li>
|
||||
* <li>Download Game</li>
|
||||
* <li>Apply JAR mods</li>
|
||||
* <li>General Setup: Compute checksum and copy 'overrides' files.</li>
|
||||
* <li>Load Components: Parse all local Json-Patch and prepare to fetch others from Internet.</li>
|
||||
* <li>Resolve Json-Patch: Fetch remote Json-Patch and their dependencies.</li>
|
||||
* <li>Build Artifact: Transform Json-Patch to Official Scheme lossily, without original structure.</li>
|
||||
* <li>Copy Embedded Files: Copy embedded libraries and icon.</li>
|
||||
* <li>Assemble Game: Prepare to download main jar, libraries and assets.</li>
|
||||
* <li>Download Game: Download files.</li>
|
||||
* <li>Apply JAR mods: Apply JAR mods into main jar.</li>
|
||||
* </ul>
|
||||
* See codes below for detailed implementation.
|
||||
*
|
||||
* @implNote To guarantee all features of MultiMC Modpack Scheme is super hard.
|
||||
* As f*** MMC never provides a detailed API docs, most codes below is guessed from its source code.
|
||||
* <b>FUNCTIONS OF GAMES MIGHT NOT BE COMPLETELY THE SAME WITH MMC.</b>
|
||||
* </p>
|
||||
*/
|
||||
public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.ResolvedInstance> {
|
||||
@@ -133,10 +142,23 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
|
||||
try (FileSystem fs = openModpack()) {
|
||||
Path root = getRootPath(fs);
|
||||
|
||||
List<Task<MultiMCInstancePatch>> patches = new ArrayList<>();
|
||||
for (MultiMCManifest.MultiMCManifestComponent component : Objects.requireNonNull(
|
||||
List<MultiMCManifest.MultiMCManifestComponent> components = Objects.requireNonNull(
|
||||
Objects.requireNonNull(manifest.getMmcPack(), "mmc-pack.json").getComponents(), "components"
|
||||
)) {
|
||||
);
|
||||
List<Task<MultiMCInstancePatch>> patches = new ArrayList<>();
|
||||
|
||||
String mcVersion = null;
|
||||
for (MultiMCManifest.MultiMCManifestComponent component : components) {
|
||||
if (MultiMCComponents.getComponent(component.getUid()) == LibraryAnalyzer.LibraryType.MINECRAFT) {
|
||||
mcVersion = component.getVersion();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mcVersion == null) {
|
||||
throw new IllegalStateException("Cannot load modpacks without Minecraft.");
|
||||
}
|
||||
|
||||
for (MultiMCManifest.MultiMCManifestComponent component : components) {
|
||||
String componentID = Objects.requireNonNull(component.getUid(), "Component ID");
|
||||
Path patchPath = root.resolve(String.format("patches/%s.json", componentID));
|
||||
|
||||
@@ -149,20 +171,22 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
|
||||
patches.add(Task.supplyAsync(() -> patch)); // TODO: Task.completed has unclear compatibility issue.
|
||||
} else {
|
||||
patches.add(
|
||||
new GetTask(MultiMCComponents.getMetaURL(componentID, component.getVersion()))
|
||||
new GetTask(MultiMCComponents.getMetaURL(componentID, component.getVersion(), mcVersion))
|
||||
.thenApplyAsync(s -> MultiMCInstancePatch.read(componentID, s))
|
||||
);
|
||||
}
|
||||
}
|
||||
dependents.add(new MMCInstancePatchesAssembleTask(patches));
|
||||
dependents.add(new MMCInstancePatchesAssembleTask(patches, mcVersion));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MMCInstancePatchesAssembleTask extends Task<List<MultiMCInstancePatch>> {
|
||||
private final List<Task<MultiMCInstancePatch>> patches;
|
||||
private final String mcVersion;
|
||||
|
||||
public MMCInstancePatchesAssembleTask(List<Task<MultiMCInstancePatch>> patches) {
|
||||
public MMCInstancePatchesAssembleTask(List<Task<MultiMCInstancePatch>> patches, String mcVersion) {
|
||||
this.patches = patches;
|
||||
this.mcVersion = mcVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -172,7 +196,7 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
Map<String, MultiMCInstancePatch> existed = new HashMap<>();
|
||||
Map<String, MultiMCInstancePatch> existed = new LinkedHashMap<>();
|
||||
for (Task<MultiMCInstancePatch> patch : patches) {
|
||||
MultiMCInstancePatch result = patch.getResult();
|
||||
|
||||
@@ -186,7 +210,7 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
|
||||
String componentID = require.getID();
|
||||
if (!existed.containsKey(componentID)) {
|
||||
Task<MultiMCInstancePatch> task = new GetTask(MultiMCComponents.getMetaURL(
|
||||
componentID, Lang.requireNonNullElse(require.getEqualsVersion(), require.getSuggests())
|
||||
componentID, Lang.requireNonNullElse(require.getEqualsVersion(), require.getSuggests()), mcVersion
|
||||
)).thenApplyAsync(s -> MultiMCInstancePatch.read(componentID, s));
|
||||
task.run();
|
||||
|
||||
@@ -231,6 +255,26 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
|
||||
if (Files.exists(libraries))
|
||||
FileUtils.copyDirectory(libraries, repository.getVersionRoot(name).toPath().resolve("libraries"));
|
||||
|
||||
for (Library library : artifact.getVersion().getLibraries()) {
|
||||
if ("local".equals(library.getHint())) {
|
||||
/* TODO: Determine whether we should erase community fields, like 'hint' and 'filename' from version json.
|
||||
Retain them will facilitate compatibility, as some embedded libraries may check where their JAR is.
|
||||
Meanwhile, potential compatibility issue with other launcher which never supports these fields might occur.
|
||||
Here, we make the file stored twice, to keep maximum compatibility. */
|
||||
Path from = repository.getLibraryFile(artifact.getVersion(), library).toPath();
|
||||
Path target = repository.getLibraryFile(artifact.getVersion(), library.withoutCommunityFields()).toPath();
|
||||
Files.createDirectories(target.getParent());
|
||||
Files.copy(from, target, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
|
||||
try (InputStream input = MaintainTask.class.getResourceAsStream("/assets/game/HMCLMultiMCBootstrap-1.0.jar")) {
|
||||
Path libraryPath = repository.getLibraryFile(artifact.getVersion(), MultiMCInstancePatch.BOOTSTRAP_LIBRARY).toPath();
|
||||
|
||||
Files.createDirectories(libraryPath.getParent());
|
||||
Files.copy(Objects.requireNonNull(input, "Bundled HMCLMultiMCBootstrap is missing."), libraryPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
String iconKey = this.manifest.getIconKey();
|
||||
if (iconKey != null) {
|
||||
Path iconFile = root.resolve(iconKey + ".png");
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user