From 4ba4c3e749ba7f5634bb370aed2eb8158670a71e Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Sat, 20 Aug 2022 15:52:22 +0800 Subject: [PATCH] feat(quilt): WIP: partially implements quilt installation. --- .../download/BMCLAPIDownloadProvider.java | 4 + .../hmcl/download/LibraryAnalyzer.java | 1 + .../hmcl/download/quilt/QuiltInstallTask.java | 203 ++++++++++++++++++ .../download/quilt/QuiltRemoteVersion.java | 45 ++++ .../hmcl/download/quilt/QuiltVersionList.java | 109 ++++++++++ .../multimc/MultiMCModpackInstallTask.java | 6 + 6 files changed, 368 insertions(+) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltInstallTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltRemoteVersion.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltVersionList.java diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java index 3c89c7460..18d172e56 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java @@ -33,6 +33,7 @@ public class BMCLAPIDownloadProvider implements DownloadProvider { private final GameVersionList game; private final FabricVersionList fabric; private final FabricAPIVersionList fabricApi; + private final QuiltVersionList quilt; private final ForgeBMCLVersionList forge; private final LiteLoaderBMCLVersionList liteLoader; private final OptiFineBMCLVersionList optifine; @@ -42,6 +43,7 @@ public class BMCLAPIDownloadProvider implements DownloadProvider { this.game = new GameVersionList(this); this.fabric = new FabricVersionList(this); this.fabricApi = new FabricAPIVersionList(this); + this.quilt = new QuiltVersionList(this); this.forge = new ForgeBMCLVersionList(apiRoot); this.liteLoader = new LiteLoaderBMCLVersionList(this); this.optifine = new OptiFineBMCLVersionList(apiRoot); @@ -72,6 +74,8 @@ public class BMCLAPIDownloadProvider implements DownloadProvider { return fabricApi; case "forge": return forge; + case "quilt": + return quilt; case "liteloader": return liteLoader; case "optifine": diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java index 010cf8cdc..7cc903b15 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -161,6 +161,7 @@ public final class LibraryAnalyzer implements Iterable[0-9.]+)(-([0-9.]+))?$"); 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 new file mode 100644 index 000000000..1772ce330 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltInstallTask.java @@ -0,0 +1,203 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.quilt; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.LibraryAnalyzer; +import org.jackhuang.hmcl.download.UnsupportedInstallationException; +import org.jackhuang.hmcl.download.fabric.FabricRemoteVersion; +import org.jackhuang.hmcl.game.Arguments; +import org.jackhuang.hmcl.game.Artifact; +import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.GetTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.gson.JsonUtils; + +import java.util.*; + +import static org.jackhuang.hmcl.download.UnsupportedInstallationException.FABRIC_NOT_COMPATIBLE_WITH_FORGE; + +/** + * Note: Quilt should be installed first. + * + * @author huangyuhui + */ +public final class QuiltInstallTask extends Task { + + private final DefaultDependencyManager dependencyManager; + private final Version version; + private final QuiltRemoteVersion remote; + private final GetTask launchMetaTask; + private final List> dependencies = new ArrayList<>(1); + + public QuiltInstallTask(DefaultDependencyManager dependencyManager, Version version, QuiltRemoteVersion remoteVersion) { + this.dependencyManager = dependencyManager; + this.version = version; + this.remote = remoteVersion; + + launchMetaTask = new GetTask(dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls())); + launchMetaTask.setCacheRepository(dependencyManager.getCacheRepository()); + } + + @Override + public boolean doPreExecute() { + return true; + } + + @Override + public void preExecute() throws Exception { + if (!Objects.equals("net.minecraft.client.main.Main", version.resolve(dependencyManager.getGameRepository()).getMainClass())) + throw new UnsupportedInstallationException(FABRIC_NOT_COMPATIBLE_WITH_FORGE); + } + + @Override + public Collection> getDependents() { + return Collections.singleton(launchMetaTask); + } + + @Override + public Collection> getDependencies() { + return dependencies; + } + + @Override + public boolean isRelyingOnDependencies() { + return false; + } + + @Override + public void execute() { + setResult(getPatch(JsonUtils.GSON.fromJson(launchMetaTask.getResult(), FabricInfo.class), remote.getGameVersion(), remote.getSelfVersion())); + + dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult(), true)); + } + + private Version getPatch(FabricInfo fabricInfo, String gameVersion, String loaderVersion) { + JsonObject launcherMeta = fabricInfo.launcherMeta; + Arguments arguments = new Arguments(); + + String mainClass; + if (!launcherMeta.get("mainClass").isJsonObject()) { + mainClass = launcherMeta.get("mainClass").getAsString(); + } else { + mainClass = launcherMeta.get("mainClass").getAsJsonObject().get("client").getAsString(); + } + + if (launcherMeta.has("launchwrapper")) { + String clientTweaker = launcherMeta.get("launchwrapper").getAsJsonObject().get("tweakers").getAsJsonObject().get("client").getAsJsonArray().get(0).getAsString(); + arguments = arguments.addGameArguments("--tweakClass", clientTweaker); + } + + JsonObject librariesObject = launcherMeta.getAsJsonObject("libraries"); + List libraries = new ArrayList<>(); + + // "common, server" is hard coded in fabric installer. + // Don't know the purpose of ignoring client libraries. + for (String side : new String[]{"common", "server"}) { + for (JsonElement element : librariesObject.getAsJsonArray(side)) { + libraries.add(JsonUtils.GSON.fromJson(element, Library.class)); + } + } + + 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); + } + + public static class FabricInfo { + private final LoaderInfo loader; + private final IntermediaryInfo hashed; + private final JsonObject launcherMeta; + + public FabricInfo(LoaderInfo loader, IntermediaryInfo intermediary, JsonObject launcherMeta) { + this.loader = loader; + this.intermediary = intermediary; + this.launcherMeta = launcherMeta; + } + + public LoaderInfo getLoader() { + return loader; + } + + public IntermediaryInfo getIntermediary() { + return intermediary; + } + + public JsonObject getLauncherMeta() { + return launcherMeta; + } + } + + public static class LoaderInfo { + private final String separator; + private final int build; + private final String maven; + private final String version; + private final boolean stable; + + public LoaderInfo(String separator, int build, String maven, String version, boolean stable) { + this.separator = separator; + this.build = build; + this.maven = maven; + this.version = version; + this.stable = stable; + } + + public String getSeparator() { + return separator; + } + + public int getBuild() { + return build; + } + + public String getMaven() { + return maven; + } + + public String getVersion() { + return version; + } + + public boolean isStable() { + return stable; + } + } + + public static class IntermediaryInfo { + private final String maven; + private final String version; + + public IntermediaryInfo(String maven, String version) { + this.maven = maven; + this.version = version; + } + + public String getMaven() { + return maven; + } + + public String getVersion() { + return version; + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltRemoteVersion.java new file mode 100644 index 000000000..c27e96498 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltRemoteVersion.java @@ -0,0 +1,45 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2022 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.quilt; + +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.LibraryAnalyzer; +import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.download.fabric.FabricInstallTask; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; + +import java.util.List; + +public class QuiltRemoteVersion extends RemoteVersion { + /** + * Constructor. + * + * @param gameVersion the Minecraft version that this remote version suits. + * @param selfVersion the version string of the remote version. + * @param urls the installer or universal jar original URL. + */ + QuiltRemoteVersion(String gameVersion, String selfVersion, List urls) { + super(LibraryAnalyzer.LibraryType.FABRIC.getPatchId(), gameVersion, selfVersion, null, urls); + } + + @Override + public Task getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) { + return new QuiltInstallTask(dependencyManager, baseVersion, this); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltVersionList.java new file mode 100644 index 000000000..76b481a16 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltVersionList.java @@ -0,0 +1,109 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2022 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.quilt; + +import com.google.gson.reflect.TypeToken; +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.VersionList; +import org.jackhuang.hmcl.download.fabric.FabricRemoteVersion; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.NetworkUtils; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import static org.jackhuang.hmcl.util.Lang.wrap; + +public final class QuiltVersionList extends VersionList { + private final DownloadProvider downloadProvider; + + public QuiltVersionList(DownloadProvider downloadProvider) { + this.downloadProvider = downloadProvider; + } + + @Override + public boolean hasType() { + return false; + } + + @Override + public CompletableFuture refreshAsync() { + return CompletableFuture.runAsync(wrap(() -> { + List gameVersions = getGameVersions(GAME_META_URL); + List loaderVersions = getGameVersions(LOADER_META_URL); + + lock.writeLock().lock(); + + try { + for (String gameVersion : gameVersions) + for (String loaderVersion : loaderVersions) + versions.put(gameVersion, new QuiltRemoteVersion(gameVersion, loaderVersion, + Collections.singletonList(getLaunchMetaUrl(gameVersion, loaderVersion)))); + } finally { + lock.writeLock().unlock(); + } + })); + } + + private static final String LOADER_META_URL = "https://meta.quiltmc.org/v3/versions/loader"; + private static final String GAME_META_URL = "https://meta.quiltmc.org/v3/versions/game"; + + private List getGameVersions(String metaUrl) throws IOException { + String json = NetworkUtils.doGet(NetworkUtils.toURL(downloadProvider.injectURL(metaUrl))); + return JsonUtils.GSON.>fromJson(json, new TypeToken>() { + }.getType()).stream().map(GameVersion::getVersion).collect(Collectors.toList()); + } + + private static String getLaunchMetaUrl(String gameVersion, String loaderVersion) { + return String.format("https://meta.quiltmc.org/v3/versions/loader/%s/%s", gameVersion, loaderVersion); + } + + private static class GameVersion { + private final String version; + private final String maven; + private final boolean stable; + + public GameVersion() { + this("", null, false); + } + + public GameVersion(String version, String maven, boolean stable) { + this.version = version; + this.maven = maven; + this.stable = stable; + } + + public String getVersion() { + return version; + } + + @Nullable + public String getMaven() { + return maven; + } + + public boolean isStable() { + return stable; + } + } +} 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 3f3281042..c23d460c2 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 @@ -89,6 +89,12 @@ public final class MultiMCModpackInstallTask extends Task { if (c.getVersion() != null) builder.version("fabric", c.getVersion()); }); + + Optional quilt = manifest.getMmcPack().getComponents().stream().filter(e -> e.getUid().equals("net.quiltmc.quilt-loader")).findAny(); + quilt.ifPresent(c -> { + if (c.getVersion() != null) + builder.version("quilt", c.getVersion()); + }); } dependents.add(builder.buildAsync());