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 55869eb12..06baad358 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 @@ -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 jarMods; - public MultiMCInstancePatch(int formatVersion, String id, String version, AssetIndexInfo assetIndex, String minecraftArguments, List jvmArgs, String mainClass, int[] javaMajors, Library mainJar, List traits, List tweakers, List libraries0, List libraries1, List mavenFiles, List jarMods) { + @SerializedName("requires") + private final List requires; + + public MultiMCInstancePatch(int formatVersion, String id, String version, AssetIndexInfo assetIndex, String minecraftArguments, List jvmArgs, String mainClass, int[] javaMajors, Library mainJar, List traits, List tweakers, List libraries0, List libraries1, List mavenFiles, List jarMods, List 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 getRequires() { + return nonNullOrEmpty(requires); + } + private static List nonNullOrEmpty(List 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()) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCManifest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCManifest.java index 3fecb7ebd..a4ac2d1e4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCManifest.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCManifest.java @@ -86,7 +86,7 @@ public final class MultiMCManifest { return equalsVersion; } - public String getUid() { + public String getID() { return uid; } 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 64c531355..2efdf0275 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 @@ -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; /** *

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. + *

    + *
  • General Setup
  • + *
  • Load Components
  • + *
  • Resolve Json-Patch
  • + *
  • Build Artifact
  • + *
  • Copy Embedded Files
  • + *
  • Assemble Game
  • + *
  • Download Game
  • + *
  • Apply JAR mods
  • + *
* See codes below for detailed implementation. *

*/ @@ -64,7 +74,6 @@ public final class MultiMCModpackInstallTask extends Task> patches = new ArrayList<>(); private final List> dependents = new ArrayList<>(); private final List> dependencies = new ArrayList<>(); private final DefaultDependencyManager dependencyManager; @@ -125,29 +134,74 @@ public final class MultiMCModpackInstallTask extends Task> 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 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> { + private final List> patches; + + public MMCInstancePatchesAssembleTask(List> patches) { + this.patches = patches; + } + + @Override + public Collection> getDependents() { + return patches; + } + + @Override + public void execute() throws Exception { + Map existed = new HashMap<>(); + for (Task 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 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 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 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()) {