解析 MultiMC 整合包中未声明但在依赖链上的组件 (#4003)

This commit is contained in:
Burning_TNT 2025-06-14 21:13:35 +08:00 committed by Glavo
parent fa8b3a952b
commit d624086594
3 changed files with 88 additions and 18 deletions

View File

@ -45,6 +45,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -96,7 +97,10 @@ public final class MultiMCInstancePatch {
@SerializedName("jarMods")
private final List<Library> jarMods;
public MultiMCInstancePatch(int formatVersion, String id, String version, AssetIndexInfo assetIndex, String minecraftArguments, List<String> jvmArgs, String mainClass, int[] javaMajors, Library mainJar, List<String> traits, List<String> tweakers, List<Library> libraries0, List<Library> libraries1, List<Library> mavenFiles, List<Library> jarMods) {
@SerializedName("requires")
private final List<MultiMCManifest.MultiMCManifestCachedRequires> requires;
public MultiMCInstancePatch(int formatVersion, String id, String version, AssetIndexInfo assetIndex, String minecraftArguments, List<String> jvmArgs, String mainClass, int[] javaMajors, Library mainJar, List<String> traits, List<String> tweakers, List<Library> libraries0, List<Library> libraries1, List<Library> mavenFiles, List<Library> jarMods, List<MultiMCManifest.MultiMCManifestCachedRequires> requires) {
this.formatVersion = formatVersion;
this.id = id;
this.version = version;
@ -112,6 +116,7 @@ public final class MultiMCInstancePatch {
this.libraries1 = libraries1;
this.mavenFiles = mavenFiles;
this.jarMods = jarMods;
this.requires = requires;
}
public int getFormatVersion() {
@ -177,6 +182,10 @@ public final class MultiMCInstancePatch {
return nonNullOrEmpty(jarMods);
}
public List<MultiMCManifest.MultiMCManifestCachedRequires> getRequires() {
return nonNullOrEmpty(requires);
}
private static <T> List<T> nonNullOrEmpty(List<T> value) {
return value != null && !value.isEmpty() ? value : Collections.emptyList();
}
@ -257,6 +266,8 @@ public final class MultiMCInstancePatch {
}
for (MultiMCInstancePatch patch : patches) {
Objects.requireNonNull(patch, "patch");
if (patch.getFormatVersion() != 1) {
throw new UnsupportedOperationException(
String.format("Unsupported JSON-Patch[%s] format version: %d", patch.getID(), patch.getFormatVersion())

View File

@ -86,7 +86,7 @@ public final class MultiMCManifest {
return equalsVersion;
}
public String getUid() {
public String getID() {
return uid;
}

View File

@ -44,16 +44,26 @@ import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* <p>A task transforming MultiMC Modpack Scheme to Official Launcher Scheme.
* The transforming process contains 7 stage:
* General Setup, Load Components, Resolve Json-Patch, Build Artifact,
* Copy Embedded Files, Assemble Game, Download Game and Apply JAR mods.
* <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>
* </ul>
* See codes below for detailed implementation.
* </p>
*/
@ -64,7 +74,6 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
private final MultiMCInstanceConfiguration manifest;
private final String name;
private final DefaultGameRepository repository;
private final List<Task<MultiMCInstancePatch>> patches = new ArrayList<>();
private final List<Task<?>> dependents = new ArrayList<>();
private final List<Task<?>> dependencies = new ArrayList<>();
private final DefaultDependencyManager dependencyManager;
@ -125,29 +134,74 @@ 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(
Objects.requireNonNull(manifest.getMmcPack(), "mmc-pack.json").getComponents(), "components"
)) {
String componentID = Objects.requireNonNull(component.getUid(), "Component ID");
Path patchPath = root.resolve(String.format("patches/%s.json", componentID));
Task<String> task;
if (Files.exists(patchPath)) {
if (!Files.isRegularFile(patchPath)) {
throw new IllegalArgumentException("Json-Patch isn't a file: " + componentID);
}
// TODO: Task.completed has unclear compatibility issue.
String text = FileUtils.readText(patchPath, StandardCharsets.UTF_8);
task = Task.supplyAsync(() -> text);
MultiMCInstancePatch patch = MultiMCInstancePatch.read(componentID, FileUtils.readText(patchPath, StandardCharsets.UTF_8));
patches.add(Task.supplyAsync(() -> patch)); // TODO: Task.completed has unclear compatibility issue.
} else {
task = new GetTask(MultiMCComponents.getMetaURL(componentID, component.getVersion()));
patches.add(
new GetTask(MultiMCComponents.getMetaURL(componentID, component.getVersion()))
.thenApplyAsync(s -> MultiMCInstancePatch.read(componentID, s))
);
}
}
dependents.add(new MMCInstancePatchesAssembleTask(patches));
}
}
private static final class MMCInstancePatchesAssembleTask extends Task<List<MultiMCInstancePatch>> {
private final List<Task<MultiMCInstancePatch>> patches;
public MMCInstancePatchesAssembleTask(List<Task<MultiMCInstancePatch>> patches) {
this.patches = patches;
}
@Override
public Collection<? extends Task<?>> getDependents() {
return patches;
}
@Override
public void execute() throws Exception {
Map<String, MultiMCInstancePatch> existed = new HashMap<>();
for (Task<MultiMCInstancePatch> patch : patches) {
MultiMCInstancePatch result = patch.getResult();
existed.put(result.getID(), result);
}
checking:
while (true) {
for (MultiMCInstancePatch patch : existed.values()) {
for (MultiMCManifest.MultiMCManifestCachedRequires require : patch.getRequires()) {
String componentID = require.getID();
if (!existed.containsKey(componentID)) {
Task<MultiMCInstancePatch> task = new GetTask(MultiMCComponents.getMetaURL(
componentID, Lang.requireNonNullElse(require.getEqualsVersion(), require.getSuggests())
)).thenApplyAsync(s -> MultiMCInstancePatch.read(componentID, s));
task.run();
MultiMCInstancePatch result = Objects.requireNonNull(task.getResult());
existed.put(result.getID(), result);
continue checking;
}
}
}
Task<MultiMCInstancePatch> task2 = task.thenApplyAsync(s -> MultiMCInstancePatch.read(componentID, s));
patches.add(task2);
dependents.add(task2);
break;
}
setResult(new ArrayList<>(existed.values()));
}
}
@ -160,10 +214,15 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
@Override
public void execute() throws Exception {
// Stage #3: Build Json-Patch artifact.
MultiMCInstancePatch.ResolvedInstance artifact = MultiMCInstancePatch.resolveArtifact(patches.stream()
.map(value -> Objects.requireNonNull(value.getResult(), "MultiMCInstancePatch"))
.collect(Collectors.toList()), name
);
MultiMCInstancePatch.ResolvedInstance artifact = null;
for (int i = dependents.size() - 1; i >= 0; i--) {
Task<?> task = dependents.get(i);
if (task instanceof MMCInstancePatchesAssembleTask) {
artifact = MultiMCInstancePatch.resolveArtifact(((MMCInstancePatchesAssembleTask) task).getResult(), name);
break;
}
}
Objects.requireNonNull(artifact, "artifact");
// Stage #4: Copy embedded files.
try (FileSystem fs = openModpack()) {