diff --git a/src/main/java/org/quiltmc/installer/CliInstaller.java b/src/main/java/org/quiltmc/installer/CliInstaller.java index 7da1650..cee3fd6 100644 --- a/src/main/java/org/quiltmc/installer/CliInstaller.java +++ b/src/main/java/org/quiltmc/installer/CliInstaller.java @@ -20,7 +20,6 @@ import java.util.LinkedList; import java.util.Map; import java.util.Queue; -import java.util.Set; import org.jetbrains.annotations.Nullable; import org.quiltmc.installer.action.Action; @@ -70,7 +69,7 @@ private static Action parse(String input) { switch (arg) { case "help": return Action.DISPLAY_HELP; - case "listVersions": + case "listVersions": { if (split.size() == 0) { System.err.println("Loader type is required"); return Action.DISPLAY_HELP; @@ -78,6 +77,7 @@ private static Action parse(String input) { String rawType = split.remove(); LoaderType type = LoaderType.of(rawType); + int intermediaryGen = -1; if (type == null) { System.err.println("Unknown loader type: " + rawType); @@ -85,7 +85,7 @@ private static Action parse(String input) { } if (split.size() == 0) { - return Action.listVersions(type, false, false); + return Action.listVersions(type, intermediaryGen, false, false); } boolean minecraftSnapshots = false; @@ -102,13 +102,38 @@ private static Action parse(String input) { } else if (option.startsWith("--")) { System.err.printf("Invalid option \"%s\"%n", arg); hasError = true; + } else if (option.startsWith("--intermediary-generation")) { + if (option.indexOf('=') == -1) { + System.err.println("Option \"--intermediary-generation\" must specify a value"); + return Action.DISPLAY_HELP; + } + + if (!option.startsWith("--intermediary-generation=")) { + System.err.println("Option \"--intermediary-generation\" must have a equals sign (=) right after the option name to get the value"); + return Action.DISPLAY_HELP; + } + + String value = unqoute(option.substring(26)); + + if (value == null) { + System.err.println("Option \"--intermediary-generation\" must have value quoted at start and end of value"); + return Action.DISPLAY_HELP; + } + + intermediaryGen = Integer.parseInt(value); + + if (intermediaryGen < 1 || intermediaryGen > IntermediaryGenerations.latest()) { + System.err.println("Option \"--intermediary-generation\" value \"" + intermediaryGen + "\" is not valid! Value must be between 1 and " + IntermediaryGenerations.latest() + "."); + return Action.DISPLAY_HELP; + } } else { System.err.printf("Unexpected additional argument \"%s\"%n", arg); hasError = true; } } - return !hasError ? Action.listVersions(type, minecraftSnapshots, loaderBetas) : Action.DISPLAY_HELP; + return !hasError ? Action.listVersions(type, intermediaryGen, minecraftSnapshots, loaderBetas) : Action.DISPLAY_HELP; + } case "install": if (split.size() == 0) { System.err.println("Side is required: \"client\" or \"server\""); @@ -137,6 +162,7 @@ private static Action parse(String input) { String rawLoaderType = split.remove(); LauncherType launcherType = LauncherType.of(rawLauncherType); LoaderType loaderType = LoaderType.of(rawLoaderType); + int intermediaryGen = -1; if (launcherType == null) { System.err.println("Unknown launcher type: " + rawLauncherType); @@ -147,11 +173,9 @@ private static Action parse(String input) { return Action.DISPLAY_HELP; } - String intermediary = fetchIntermediary(GameSide.CLIENT, minecraftVersion); - // At this point all the require arguments have been parsed if (split.size() == 0) { - return Action.installClient(minecraftVersion, launcherType, loaderType, null, intermediary, null, false, false); + return Action.installClient(minecraftVersion, launcherType, loaderType, null, intermediaryGen, null, null, false, false); } // Try to parse loader version first @@ -166,7 +190,7 @@ private static Action parse(String input) { // No more arguments, just loader version if (split.size() == 0) { - return Action.installClient(minecraftVersion, launcherType, loaderType, loaderVersion, intermediary, null, false, false); + return Action.installClient(minecraftVersion, launcherType, loaderType, loaderVersion, intermediaryGen, null, null, false, false); } // There are some additional options @@ -218,13 +242,37 @@ private static Action parse(String input) { } options.put("--install-dir", value); + } else if (option.startsWith("--intermediary-generation")) { + if (option.indexOf('=') == -1) { + System.err.println("Option \"--intermediary-generation\" must specify a value"); + return Action.DISPLAY_HELP; + } + + if (!option.startsWith("--intermediary-generation=")) { + System.err.println("Option \"--intermediary-generation\" must have a equals sign (=) right after the option name to get the value"); + return Action.DISPLAY_HELP; + } + + String value = unqoute(option.substring(26)); + + if (value == null) { + System.err.println("Option \"--intermediary-generation\" must have value quoted at start and end of value"); + return Action.DISPLAY_HELP; + } + + intermediaryGen = Integer.parseInt(value); + + if (intermediaryGen < 1 || intermediaryGen > IntermediaryGenerations.latest()) { + System.err.println("Option \"--intermediary-generation\" value \"" + intermediaryGen + "\" is not valid! Value must be between 1 and " + IntermediaryGenerations.latest() + "."); + return Action.DISPLAY_HELP; + } } else { System.err.printf("Invalid option %s%n", option); return Action.DISPLAY_HELP; } } - return Action.installClient(minecraftVersion, launcherType, loaderType, loaderVersion, intermediary, options.get("--install-dir"), !options.containsKey("--no-profile"), options.containsKey("--copy-profile-path")); + return Action.installClient(minecraftVersion, launcherType, loaderType, loaderVersion, intermediaryGen, null, options.get("--install-dir"), !options.containsKey("--no-profile"), options.containsKey("--copy-profile-path")); } case "server": { if (split.size() < 1) { @@ -239,6 +287,7 @@ private static Action parse(String input) { String minecraftVersion = split.remove(); String rawLoaderType = split.remove(); LoaderType loaderType = LoaderType.of(rawLoaderType); + int intermediaryGen = -1; if (loaderType == null) { System.err.println("Unknown loader type: " + rawLoaderType); @@ -247,7 +296,7 @@ private static Action parse(String input) { // At this point all the require arguments have been parsed if (split.size() == 0) { - return Action.installServer(minecraftVersion, loaderType, null, null, false, false); + return Action.installServer(minecraftVersion, loaderType, null, intermediaryGen, null, null, false, false); } // Try to parse loader version first @@ -262,7 +311,7 @@ private static Action parse(String input) { // No more arguments, just loader version if (split.size() == 0) { - return Action.installServer(minecraftVersion, loaderType, loaderVersion, null, false, false); + return Action.installServer(minecraftVersion, loaderType, loaderVersion, intermediaryGen, null, null, false, false); } // There are some additional options @@ -313,13 +362,37 @@ private static Action parse(String input) { } options.put("--install-dir", value); + } else if (option.startsWith("--intermediary-generation")) { + if (option.indexOf('=') == -1) { + System.err.println("Option \"--intermediary-generation\" must specify a value"); + return Action.DISPLAY_HELP; + } + + if (!option.startsWith("--intermediary-generation=")) { + System.err.println("Option \"--intermediary-generation\" must have a equals sign (=) right after the option name to get the value"); + return Action.DISPLAY_HELP; + } + + String value = unqoute(option.substring(26)); + + if (value == null) { + System.err.println("Option \"--intermediary-generation\" must have value quoted at start and end of value"); + return Action.DISPLAY_HELP; + } + + intermediaryGen = Integer.parseInt(value); + + if (intermediaryGen < 1 || intermediaryGen > IntermediaryGenerations.latest()) { + System.err.println("Option \"--intermediary-generation\" value \"" + intermediaryGen + "\" is not valid! Value must be between 1 and " + IntermediaryGenerations.latest() + "."); + return Action.DISPLAY_HELP; + } } else { System.err.printf("Invalid option %s%n", option); return Action.DISPLAY_HELP; } } - return Action.installServer(minecraftVersion, loaderType, loaderVersion, options.get("--install-dir"), options.containsKey("--create-scripts"), options.containsKey("--download-server")); + return Action.installServer(minecraftVersion, loaderType, loaderVersion, intermediaryGen, null, options.get("--install-dir"), options.containsKey("--create-scripts"), options.containsKey("--download-server")); } default: System.err.printf("Invalid side \"%s\", expected \"client\" or \"server\"%n", arg); @@ -332,14 +405,6 @@ private static Action parse(String input) { } } - private static String fetchIntermediary(GameSide side, String minecraftVersion) { - return OrnitheMeta.create(OrnitheMeta.ORNITHE_META_URL, Set.of(OrnitheMeta.INTERMEDIARY_VERSIONS_ENDPOINT)).thenApply(meta -> { - VersionManifest manifest = VersionManifest.create().join(); - VersionManifest.Version version = manifest.getVersion(minecraftVersion); - return meta.getEndpoint(OrnitheMeta.INTERMEDIARY_VERSIONS_ENDPOINT).get(version.id(side)); - }).join(); - } - /** * Takes a string and splits it at spaces while leaving quoted segements unsplit. * diff --git a/src/main/java/org/quiltmc/installer/GameSide.java b/src/main/java/org/quiltmc/installer/GameSide.java index 6485501..faca601 100644 --- a/src/main/java/org/quiltmc/installer/GameSide.java +++ b/src/main/java/org/quiltmc/installer/GameSide.java @@ -17,8 +17,8 @@ package org.quiltmc.installer; public enum GameSide { - CLIENT("client", "/v3/versions/%s-loader/%s/%s/profile/json"), - SERVER("server", "/v3/versions/%s-loader/%s/%s/server/json"); + CLIENT("client", "/%s-loader/%s/%s/profile/json"), + SERVER("server", "/%s-loader/%s/%s/server/json"); private final String id; private final String launchJsonEndpoint; @@ -35,4 +35,18 @@ public String id() { public String launchJsonEndpoint() { return this.launchJsonEndpoint; } + + public String stripFromVersion(String version) { + return version.endsWith(this.id) ? version.substring(0, version.length() - (this.id.length() + 1)) : version; + } + + public boolean versionMatches(String version) { + for (GameSide side : GameSide.values()) { + if (version.endsWith(side.id)) { + return side == this; + } + } + + return true; + } } diff --git a/src/main/java/org/quiltmc/installer/Intermediary.java b/src/main/java/org/quiltmc/installer/Intermediary.java new file mode 100644 index 0000000..83a8e1c --- /dev/null +++ b/src/main/java/org/quiltmc/installer/Intermediary.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.installer; + +public class Intermediary { + + private final String version; + private final String maven; + + public Intermediary(String version, String maven) { + this.version = version; + this.maven = maven; + } + + public String getVersion() { + return this.version; + } + + public String getMavenNotation() { + return this.maven; + } +} diff --git a/src/main/java/org/quiltmc/installer/IntermediaryGenerations.java b/src/main/java/org/quiltmc/installer/IntermediaryGenerations.java new file mode 100644 index 0000000..8b5935b --- /dev/null +++ b/src/main/java/org/quiltmc/installer/IntermediaryGenerations.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.installer; + +import java.util.Set; + +import org.quiltmc.installer.OrnitheMeta.Endpoint; + +public class IntermediaryGenerations { + + static { + try { + Endpoint endpoint = OrnitheMeta.intermediaryGenerationsEndpoint(); + OrnitheMeta meta = OrnitheMeta.create(OrnitheMeta.ORNITHE_META_URL, Set.of(endpoint)).get(); + int[] gens = meta.getEndpoint(endpoint); + + latest = gens[0]; + stable = gens[1]; + } catch (Exception e) { + throw new RuntimeException("unable to fetch latest and stable intermediary generations", e); + } + } + + private static int latest; + private static int stable; + + public static int latest() { + return latest; + } + + public static int stable() { + return stable; + } +} diff --git a/src/main/java/org/quiltmc/installer/LaunchJson.java b/src/main/java/org/quiltmc/installer/LaunchJson.java index d418f8a..43d229d 100644 --- a/src/main/java/org/quiltmc/installer/LaunchJson.java +++ b/src/main/java/org/quiltmc/installer/LaunchJson.java @@ -136,11 +136,9 @@ private static Map buildPackJsonMap( * @return the launch json for a vanilla mc instance */ public static CompletableFuture get(VersionManifest.Version gameVersion) { - String rawUrl = String.format(VersionManifest.VERSION_META_URL, gameVersion.id()); - return CompletableFuture.supplyAsync(() -> { try { - URL url = new URL(rawUrl); + URL url = new URL(gameVersion.url()); URLConnection connection = Connections.openConnection(url); Map map; @@ -148,25 +146,11 @@ public static CompletableFuture get(VersionManifest.Version gameVersion) map = (Map) Gsons.read(JsonReader.json(input)); } - for (String rawManifestUrl : gameVersion.details().manifests()) { - URL manifestUrl = new URL(rawManifestUrl); - URLConnection manifestConnection = Connections.openConnection(manifestUrl); - - try (InputStreamReader input = new InputStreamReader(manifestConnection.getInputStream())) { - buildVersionJsonFromManifest(map, (Map) Gsons.read(JsonReader.json(input))); - } - } - // add the -vanilla suffix to the vanilla json 'cause // we use a different version manifest than mojang and // some version ids can differ from the official ones map.put("id", String.format("%s-vanilla", gameVersion.id())); - // remove ASM from the libraries, as it will conflict - // with Loader's ASM dependency! - List> libs = (List>) map.get("libraries"); - libs.removeIf(lib -> lib.get("name").contains("org.ow2.asm")); - StringWriter writer = new StringWriter(); Gsons.write(JsonWriter.json(writer), map); @@ -177,32 +161,11 @@ public static CompletableFuture get(VersionManifest.Version gameVersion) }); } - private static void buildVersionJsonFromManifest(Map versionJson, Map manifest) { - for (String key : manifest.keySet()) { - if (versionJson.containsKey(key)) { - Object versionJsonElement = versionJson.get(key); - Object manifestElement = manifest.get(key); - - if (versionJsonElement.equals(manifestElement)) { - // version json already contains this element, continue - } else { - // check if elements are objects and combine them - if (versionJsonElement instanceof Map && manifestElement instanceof Map) { - buildVersionJsonFromManifest((Map) versionJsonElement, (Map) manifestElement); - } - } - } else { - // version json does not have this element yet; add it - versionJson.put(key, manifest.get(key)); - } - } - } - /** * @return the launch json for a modded mc instance */ - public static CompletableFuture get(GameSide side, VersionManifest.Version gameVersion, LoaderType type, String loaderVersion) { - String rawUrl = OrnitheMeta.ORNITHE_META_URL + String.format(side.launchJsonEndpoint(), type.getName(), gameVersion.id(side), loaderVersion); + public static CompletableFuture get(GameSide side, VersionManifest.Version gameVersion, int intermediaryGen, Intermediary intermediary, LoaderType loaderType, String loaderVersion) { + String rawUrl = OrnitheMeta.ORNITHE_META_URL + OrnitheMeta.launchJsonEndpointPath(side, loaderType, loaderVersion, intermediaryGen, intermediary); return CompletableFuture.supplyAsync(() -> { try { @@ -236,11 +199,11 @@ public static CompletableFuture get(GameSide side, VersionManifest.Versi } // we apply the library upgrades only to the Ornithe instance, not the Vanilla instance - OrnitheMeta.Endpoint>> libraryUpgradesEndpoint = OrnitheMeta.libraryUpgradesEndpoint(gameVersion.id()); + OrnitheMeta.Endpoint>> libraryUpgradesEndpoint = OrnitheMeta.libraryUpgradesEndpoint(intermediaryGen, gameVersion.id()); OrnitheMeta meta = OrnitheMeta.create(OrnitheMeta.ORNITHE_META_URL, Collections.singleton(libraryUpgradesEndpoint)).join(); List> libraryUpgrades = meta.getEndpoint(libraryUpgradesEndpoint); - if (type == LoaderType.QUILT) { + if (loaderType == LoaderType.QUILT) { // Prevents a log warning about being unable to reach the active user beacon on stable versions. switch (loaderVersion) { case "0.19.2", "0.19.3", "0.19.4" -> { @@ -256,17 +219,9 @@ public static CompletableFuture get(GameSide side, VersionManifest.Versi } } - @SuppressWarnings("unchecked") List> libraries = (List>) map.get("libraries"); - for (Map library : libraries) { - if (library.get("name").startsWith("net.fabricmc:intermediary")) { - library.replace("name", library.get("name").replace("net.fabricmc:intermediary", "net.ornithemc:calamus-intermediary")); - library.replace("url", "https://maven.ornithemc.net/releases/"); - } - if (library.get("name").startsWith("org.quiltmc:hashed")) { - library.replace("name", library.get("name").replace("org.quiltmc:hashed", "net.ornithemc:calamus-intermediary")); - library.replace("url", "https://maven.ornithemc.net/releases/"); - } - } + @SuppressWarnings("unchecked") + List> libraries = (List>) map.get("libraries"); + libraries.addAll(libraryUpgrades); StringWriter writer = new StringWriter(); diff --git a/src/main/java/org/quiltmc/installer/LauncherProfiles.java b/src/main/java/org/quiltmc/installer/LauncherProfiles.java index bc26765..cab62c0 100644 --- a/src/main/java/org/quiltmc/installer/LauncherProfiles.java +++ b/src/main/java/org/quiltmc/installer/LauncherProfiles.java @@ -44,7 +44,7 @@ public final class LauncherProfiles { * @param loaderType the selected loader type * @throws IOException if there were any issues reading or writing */ - public static void updateProfiles(Path gameDir, String name, String gameVersion, LoaderType loaderType) throws IOException { + public static void updateProfiles(Path gameDir, String name, String gameVersion, LoaderType loaderType, int intermediaryGen) throws IOException { final Path launcherProfilesPath = gameDir.resolve("launcher_profiles.json"); if (Files.notExists(launcherProfilesPath)) { @@ -69,7 +69,7 @@ public static void updateProfiles(Path gameDir, String name, String gameVersion, @SuppressWarnings("unchecked") Map profiles = (Map) rawProfiles; - String newProfileName = loaderType.getLocalizedName() + " " + gameVersion; + String newProfileName = "Ornithe Gen" + intermediaryGen + " " + loaderType.getLocalizedName() + " " + gameVersion; // Modify the profile if (profiles.containsKey(newProfileName)) { diff --git a/src/main/java/org/quiltmc/installer/MmcPackCreator.java b/src/main/java/org/quiltmc/installer/MmcPackCreator.java index 03c3a43..a538ce6 100644 --- a/src/main/java/org/quiltmc/installer/MmcPackCreator.java +++ b/src/main/java/org/quiltmc/installer/MmcPackCreator.java @@ -39,21 +39,21 @@ public class MmcPackCreator { //private static final Semver VERSION_1_3 = new Semver("1.3.0-pre+07261249"); private static String findLwjglVersion(VersionManifest manifest, String gameVersion) { - for (String rawUrl : manifest.getVersion(gameVersion).details().manifests()) { - try { - URL url = new URL(rawUrl); - URLConnection connection = Connections.openConnection(url); + VersionManifest.Version version = manifest.getVersion(gameVersion); - try (JsonReader reader = JsonReader.json(new BufferedReader(new InputStreamReader(connection.getInputStream())))) { - String lwjglVersion = findLwjglVersion(reader); + try { + URL url = new URL(version.url()); + URLConnection connection = Connections.openConnection(url); - if (lwjglVersion != null) { - return lwjglVersion; - } + try (JsonReader reader = JsonReader.json(new BufferedReader(new InputStreamReader(connection.getInputStream())))) { + String lwjglVersion = findLwjglVersion(reader); + + if (lwjglVersion != null) { + return lwjglVersion; } - } catch (IOException e) { - throw new RuntimeException("issue while finding lwjgl version for Minecraft " + gameVersion, e); } + } catch (IOException e) { + throw new RuntimeException("issue while finding lwjgl version for Minecraft " + gameVersion, e); } throw new RuntimeException("unable to find lwjgl version for Minecraft " + gameVersion); @@ -140,7 +140,7 @@ private static String transformMinecraftJson(String minecraftPatchString, String .replaceAll("\\$\\{lwjgl_uid}", lwjglMajorVer.equals("3") ? "org.lwjgl3" : "org.lwjgl"); } - private static String addLibraryUpgrades(Path instanceZipRoot, String gameVersion, LoaderType loaderType, String loaderVersion, String packJson) throws IOException { + private static String addLibraryUpgrades(Path instanceZipRoot, String gameVersion, LoaderType loaderType, String loaderVersion, int intermediaryGen, Intermediary intermediary, String packJson) throws IOException { String patch = "{\"formatVersion\": 1, " + "\"libraries\": " + "[{\"name\": \"%s\"," + @@ -151,7 +151,7 @@ private static String addLibraryUpgrades(Path instanceZipRoot, String gameVersio "\"version\": \"%s\"" + "}"; OrnitheMeta.Endpoint>> librariesEndpoint = - OrnitheMeta.libraryUpgradesEndpoint(gameVersion); + OrnitheMeta.libraryUpgradesEndpoint(intermediaryGen, gameVersion); OrnitheMeta meta = OrnitheMeta.create(OrnitheMeta.ORNITHE_META_URL, Collections.singleton(librariesEndpoint)) .join(); @@ -181,7 +181,7 @@ private static String addLibraryUpgrades(Path instanceZipRoot, String gameVersio } - public static void compileMmcZip(Path outPutDir, String gameVersion, LoaderType loaderType, String loaderVersion, String intermediaryInfo, VersionManifest manifest, boolean copyProfilePath) { + public static void compileMmcZip(Path outPutDir, String gameVersion, LoaderType loaderType, String loaderVersion, int intermediaryGen, Intermediary intermediary, VersionManifest manifest, boolean copyProfilePath) { String examplePackDir = "/packformat"; String packJsonPath = "mmc-pack.json"; @@ -191,9 +191,9 @@ public static void compileMmcZip(Path outPutDir, String gameVersion, LoaderType String minecraftPatchPath = "patches/net.minecraft.json"; VersionManifest.Version version = manifest.getVersion(gameVersion); - String[] intermediaryParts = intermediaryInfo.split("[:]"); - String intermediaryMaven = intermediaryParts[0] + ":" + intermediaryParts[1]; - String intermediaryVersion = intermediaryParts[2]; + String intermediaryMavenNotation = intermediary.getMavenNotation(); + String intermediaryArtifact = intermediaryMavenNotation.substring(0, intermediaryMavenNotation.lastIndexOf(':')); + String intermediaryVersion = intermediary.getVersion(); try { String lwjglVersion = findLwjglVersion(manifest, gameVersion); @@ -204,7 +204,7 @@ public static void compileMmcZip(Path outPutDir, String gameVersion, LoaderType String transformedIntermediaryJson = readResource(examplePackDir, intermediaryJsonPath) .replaceAll("\\$\\{mc_version}", gameVersion) .replaceAll("\\$\\{intermediary_ver}", intermediaryVersion) - .replaceAll("\\$\\{intermediary_maven}", intermediaryMaven); + .replaceAll("\\$\\{intermediary_maven}", intermediaryArtifact); String transformedInstanceCfg = readResource(examplePackDir, instanceCfgPath) .replaceAll("\\$\\{mc_version}", gameVersion); @@ -227,7 +227,7 @@ public static void compileMmcZip(Path outPutDir, String gameVersion, LoaderType Files.writeString(fs.getPath(intermediaryJsonPath), transformedIntermediaryJson); Files.writeString(fs.getPath(minecraftPatchPath), transformedMinecraftJson); String packJsonWithLibraries = addLibraryUpgrades(fs.getPath("/"), gameVersion, - loaderType, loaderVersion, transformedPackJson); + loaderType, loaderVersion, intermediaryGen, intermediary, transformedPackJson); Files.writeString(fs.getPath(packJsonPath), packJsonWithLibraries); } diff --git a/src/main/java/org/quiltmc/installer/OrnitheMeta.java b/src/main/java/org/quiltmc/installer/OrnitheMeta.java index 1441126..209762a 100644 --- a/src/main/java/org/quiltmc/installer/OrnitheMeta.java +++ b/src/main/java/org/quiltmc/installer/OrnitheMeta.java @@ -25,7 +25,6 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -36,92 +35,142 @@ import org.quiltmc.parsers.json.JsonToken; public final class OrnitheMeta { - public static final Endpoint> FABRIC_LOADER_VERSIONS_ENDPOINT = createVersion("/v3/versions/fabric-loader"); - public static final Endpoint> QUILT_LOADER_VERSIONS_ENDPOINT = createVersion("/v3/versions/quilt-loader"); - @SuppressWarnings("unchecked") - public static Endpoint>> libraryUpgradesEndpoint(String gameVersion){ - return new Endpoint<>(String.format("/v3/versions/libraries/%s", gameVersion), reader -> { - return (List>) Gsons.read(reader); - }); - } - - public static Endpoint> loaderVersionsEndpoint(LoaderType type) { - switch (type) { - case FABRIC: - return FABRIC_LOADER_VERSIONS_ENDPOINT; - case QUILT: - return QUILT_LOADER_VERSIONS_ENDPOINT; - } - - throw new IllegalStateException("no endpoints for loader type " + type.getName()); - } - - /** - * An endpoint for intermediary versions. - * - *

The returned map has the version as the key and the maven artifact as the value - */ - public static final Endpoint> INTERMEDIARY_VERSIONS_ENDPOINT = new Endpoint<>("/v3/versions/intermediary", reader -> { - Map ret = new LinkedHashMap<>(); + public static Endpoint intermediaryGenerationsEndpoint() { + return deduplicate(new Endpoint<>("/intermediary_generations", reader -> { + int[] gens = new int[2]; - if (reader.peek() != JsonToken.BEGIN_ARRAY) { - throw new ParseException("Intermediary versions must be in an array", reader); - } - - reader.beginArray(); - - while (reader.hasNext()) { if (reader.peek() != JsonToken.BEGIN_OBJECT) { - throw new ParseException("Intermediary version entry must be an object", reader); + throw new ParseException("Intermediary generations must be an object", reader); } reader.beginObject(); - String version = null; - String maven = null; - while (reader.hasNext()) { if (reader.peek() != JsonToken.NAME) { reader.skipValue(); continue; } switch (reader.nextName()) { - case "version" -> { - if (reader.peek() != JsonToken.STRING) { - throw new ParseException("Version must be a string", reader); + case "latestIntermediaryGeneration" -> { + if (reader.peek() != JsonToken.NUMBER) { + throw new ParseException("Version must be a number", reader); } - version = reader.nextString(); + gens[0] = reader.nextInt(); } - case "maven" -> { - if (reader.peek() != JsonToken.STRING) { - throw new ParseException("maven must be a string", reader); + case "stableIntermediaryGeneration" -> { + if (reader.peek() != JsonToken.NUMBER) { + throw new ParseException("maven must be a number", reader); } - maven = reader.nextString(); + gens[1] = reader.nextInt(); } - case "stable" -> reader.nextBoolean(); // TODO + default -> reader.skipValue(); } } - if (version == null) { - throw new ParseException("Intermediary version entry does not have a version field", reader); + if (gens[0] == 0) { + throw new ParseException("Latest intermediary generation not found", reader); } - if (maven == null) { - throw new ParseException("Intermediary version entry does not have a maven field", reader); + if (gens[1] == 0) { + throw new ParseException("Stable intermediary generation not found", reader); } - ret.put(version, maven); - reader.endObject(); - } - reader.endArray(); + return gens; + })); + } + + @SuppressWarnings("unchecked") + public static Endpoint>> libraryUpgradesEndpoint(int intermediaryGen, String gameVersion){ + return deduplicate(new Endpoint<>(intermediaryGen, "/libraries/" + gameVersion, reader -> { + return (List>) Gsons.read(reader); + })); + } + + public static Endpoint> loaderVersionsEndpoint(int intermediaryGen, LoaderType type) { + return deduplicate(createVersion(intermediaryGen, "/" + type.getName() + "-loader")); + } + + /** + * An endpoint for intermediary versions. + * + *

The returned map has the version as the key and the maven artifact as the value + */ + public static final Endpoint> intermediaryVersionsEndpoint(int intermediaryGen) { + return deduplicate(new Endpoint<>(intermediaryGen, "/intermediary", reader -> { + List ret = new ArrayList<>(); - return ret; - }); + if (reader.peek() != JsonToken.BEGIN_ARRAY) { + throw new ParseException("Intermediary versions must be in an array", reader); + } + + reader.beginArray(); + + while (reader.hasNext()) { + if (reader.peek() != JsonToken.BEGIN_OBJECT) { + throw new ParseException("Intermediary version entry must be an object", reader); + } + + reader.beginObject(); + + String version = null; + String maven = null; + + while (reader.hasNext()) { + if (reader.peek() != JsonToken.NAME) { + reader.skipValue(); + continue; + } + switch (reader.nextName()) { + case "version" -> { + if (reader.peek() != JsonToken.STRING) { + throw new ParseException("Version must be a string", reader); + } + version = reader.nextString(); + } + case "maven" -> { + if (reader.peek() != JsonToken.STRING) { + throw new ParseException("maven must be a string", reader); + } + maven = reader.nextString(); + } + case "stable" -> reader.nextBoolean(); // TODO + } + } + + if (version == null) { + throw new ParseException("Intermediary version entry does not have a version field", reader); + } + + if (maven == null) { + throw new ParseException("Intermediary version entry does not have a maven field", reader); + } + + ret.add(new Intermediary(version, maven)); + + reader.endObject(); + } + + reader.endArray(); + + return ret; + })); + } + + public static String launchJsonEndpointPath(GameSide side, LoaderType loaderType, String loaderVersion, int intermediaryGen, Intermediary intermediary) { + return "/v3/versions" + (intermediaryGen < 1 ? "" : ("/gen" + intermediaryGen)) + String.format(side.launchJsonEndpoint(), loaderType.getName(), intermediary.getVersion(), loaderVersion); + } + + @SuppressWarnings("unchecked") + public static Endpoint deduplicate(Endpoint endpoint) { + return (Endpoint) ENDPOINTS.computeIfAbsent(endpoint.endpointPath, _key -> endpoint); + } public static final String ORNITHE_META_URL = "https://meta.ornithemc.net"; + private static final Map> ENDPOINTS = new HashMap<>(); + private final Map, Object> endpoints; public static CompletableFuture create(String baseMetaUrl, Set> endpoints) { @@ -157,8 +206,8 @@ public static CompletableFuture create(String baseMetaUrl, Set> createVersion(String endpointPath) { - return new Endpoint<>(endpointPath, reader -> { + private static Endpoint> createVersion(int intermediaryGen, String endpointPath) { + return new Endpoint<>(intermediaryGen, endpointPath, reader -> { if (reader.peek() != JsonToken.BEGIN_ARRAY) { throw new ParseException("Result of endpoint must be an object", reader); } @@ -224,8 +273,12 @@ public static final class Endpoint { private final String endpointPath; private final ThrowingFunction deserializer; + Endpoint(int intermediaryGen, String endpointPath, ThrowingFunction deserializer) { + this((intermediaryGen < 1 ? "" : ("/gen" + intermediaryGen)) + endpointPath, deserializer); + } + Endpoint(String endpointPath, ThrowingFunction deserializer) { - this.endpointPath = endpointPath; + this.endpointPath = "/v3/versions" + endpointPath; this.deserializer = deserializer; } diff --git a/src/main/java/org/quiltmc/installer/VersionManifest.java b/src/main/java/org/quiltmc/installer/VersionManifest.java index c49af0d..dcdb004 100644 --- a/src/main/java/org/quiltmc/installer/VersionManifest.java +++ b/src/main/java/org/quiltmc/installer/VersionManifest.java @@ -36,16 +36,19 @@ */ // TODO: Abstract to another library for sharing logic with meta? public final class VersionManifest implements Collection { - public static final String LAUNCHER_META_URL = "https://skyrising.github.io/mc-versions/version_manifest.json"; - public static final String VERSION_META_URL = "https://skyrising.github.io/mc-versions/version/manifest/%s.json"; + private static final String LAUNCHER_META_URL = "https://ornithemc.net/mc-versions/version_manifest.json"; + private static final String LAUNCHER_META_BY_GEN_URL = "https://ornithemc.net/mc-versions/gen2/version_manifest.json"; + private final Version latestRelease; private final Version latestSnapshot; private final Map versions; - public static CompletableFuture create() { + public static CompletableFuture create(int intermediaryGen) { return CompletableFuture.supplyAsync(() -> { try { - URL url = new URL(LAUNCHER_META_URL); + URL url = new URL(intermediaryGen < 1 + ? LAUNCHER_META_URL + : String.format(LAUNCHER_META_BY_GEN_URL, intermediaryGen)); URLConnection connection = Connections.openConnection(url); InputStreamReader stream = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8); @@ -144,7 +147,6 @@ private static void readVersions(JsonReader reader, Map version String url = null; String time = null; String releaseTime = null; - String details = null; while (reader.hasNext()) { String key = reader.nextName(); @@ -185,13 +187,6 @@ private static void readVersions(JsonReader reader, Map version releaseTime = reader.nextString(); break; - case "details": - if (reader.peek() != JsonToken.STRING) { - throw new ParseException("Details url must be a string", reader); - } - - details = reader.nextString(); - break; // v2 adds sha1 and complianceLevel, we do not need those default: reader.skipValue(); @@ -207,117 +202,11 @@ private static void readVersions(JsonReader reader, Map version // always have this information in the manifest so we just ignore it // if (time == null) throw new ParseException("Version time is required", reader); // if (releaseTime == null) throw new ParseException("Version release time is required", reader); - if (details == null) throw new ParseException("Details url is required", reader); - - versions.put(id, new Version(id, type, url, time, releaseTime, details)); - } - - reader.endArray(); - } - - private static VersionDetails readDetails(Version version) { - try { - URL url = new URL(version.detailsUrl); - URLConnection connection = url.openConnection(); - - InputStreamReader stream = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8); - - try (JsonReader reader = JsonReader.json(new BufferedReader(stream))) { - return readDetails(version, reader); - } - } catch (IOException e) { - throw new UncheckedIOException(e); // Handled via .exceptionally(...) - } - } - - private static VersionDetails readDetails(Version version, JsonReader reader) throws IOException, ParseException { - if (reader.peek() != JsonToken.BEGIN_OBJECT) { - throw new ParseException("Version Details was invalid type", reader); - } - - List manifests = null; - Boolean sharedMappings = null; - String normalizedVersion = null; - - reader.beginObject(); - - while (reader.hasNext()) { - String key = reader.nextName(); - - switch (key) { - case "manifests": - if (reader.peek() != JsonToken.BEGIN_ARRAY) { - throw new ParseException("manifests must be an array", reader); - } - - manifests = readManifests(version, reader); - break; - case "sharedMappings": - if (reader.peek() != JsonToken.BOOLEAN) { - throw new ParseException("sharedMappings must be a boolean", reader); - } - - sharedMappings = reader.nextBoolean(); - break; - case "normalizedVersion": - if(reader.peek() != JsonToken.STRING) { - throw new ParseException("normalizedVersion must be a string", reader); - } - - normalizedVersion = reader.nextString(); - break; - default: - reader.skipValue(); - } - } - - reader.endObject(); - - if (manifests == null) throw new ParseException("manifests is required", reader); - if (sharedMappings == null) throw new ParseException("sharedMappings is required", reader); - if (normalizedVersion == null) throw new ParseException("normalizedVersion is required", reader); - - return new VersionDetails(version, normalizedVersion, manifests, sharedMappings); - } - - private static List readManifests(Version version, JsonReader reader) throws IOException, ParseException { - if (reader.peek() != JsonToken.BEGIN_ARRAY) { - throw new ParseException("Versions manifests must be in an array", reader); - } - - List manifests = new ArrayList<>(); - - reader.beginArray(); - - while (reader.hasNext()) { - if (reader.peek() != JsonToken.BEGIN_OBJECT) { - throw new ParseException("Version manifest entries must all be objects", reader); - } - - reader.beginObject(); - - while (reader.hasNext()) { - String key = reader.nextName(); - - switch (key) { - case "url": - if (reader.peek() != JsonToken.STRING) { - throw new ParseException("Version url must be a string", reader); - } - - manifests.add(reader.nextString()); - break; - default: - reader.skipValue(); - } - } - reader.endObject(); + versions.put(id, new Version(id, type, url, time, releaseTime)); } reader.endArray(); - - return Collections.unmodifiableList(manifests); } private VersionManifest(Version latestRelease, Version latestSnapshot, Map versions) { @@ -437,27 +326,19 @@ public static final class Version { private final String url; private final String time; private final String releaseTime; - private final String detailsUrl; - - private VersionDetails details; - Version(String id, String type, String url, String time, String releaseTime, String details) { + Version(String id, String type, String url, String time, String releaseTime) { this.id = id; this.type = type; this.url = url; this.time = time; this.releaseTime = releaseTime; - this.detailsUrl = details; } public String id() { return this.id; } - public String id(GameSide side) { - return this.details().sharedMappings() ? this.id : (this.id + "-" + side.id()); - } - public String type() { return this.type; } @@ -473,43 +354,5 @@ public String time() { public String releaseTime() { return this.releaseTime; } - - public VersionDetails details() { - if (this.details == null) { - this.details = readDetails(this); - } - - return this.details; - } - } - - public static final class VersionDetails { - private final Version version; - private final String normalizedVersion; - private final List manifests; - private final boolean sharedMappings; - - VersionDetails(Version version, String normalizedVersion, List manifests, boolean sharedMappings) { - this.version = version; - this.normalizedVersion = normalizedVersion; - this.manifests = manifests; - this.sharedMappings = sharedMappings; - } - - public Version version() { - return this.version; - } - - public String normalizedVersion() { - return normalizedVersion; - } - - public List manifests() { - return this.manifests; - } - - public boolean sharedMappings() { - return this.sharedMappings; - } } } diff --git a/src/main/java/org/quiltmc/installer/action/Action.java b/src/main/java/org/quiltmc/installer/action/Action.java index 50fb2f2..f7a6f81 100644 --- a/src/main/java/org/quiltmc/installer/action/Action.java +++ b/src/main/java/org/quiltmc/installer/action/Action.java @@ -28,6 +28,7 @@ import org.jetbrains.annotations.Nullable; import org.quiltmc.installer.Localization; import org.quiltmc.installer.CliInstaller; +import org.quiltmc.installer.Intermediary; import org.quiltmc.installer.LauncherType; import org.quiltmc.installer.LoaderType; @@ -86,16 +87,16 @@ private void printHelp() { } }; - public static Action listVersions(LoaderType loaderType, boolean minecraftSnapshots, boolean loaderBetas) { - return new ListVersions(loaderType, minecraftSnapshots, loaderBetas); + public static Action listVersions(LoaderType loaderType, int intermediaryGen, boolean minecraftSnapshots, boolean loaderBetas) { + return new ListVersions(loaderType, intermediaryGen, minecraftSnapshots, loaderBetas); } - public static InstallClient installClient(String minecraftVersion, LauncherType launcherType, LoaderType loaderType, @Nullable String loaderVersion, String intermediary, @Nullable String installDir, boolean generateProfile, boolean copyProfilePath) { - return new InstallClient(minecraftVersion, launcherType, loaderType, loaderVersion, intermediary, installDir, generateProfile, copyProfilePath); + public static InstallClient installClient(String minecraftVersion, LauncherType launcherType, LoaderType loaderType, @Nullable String loaderVersion, int intermediaryGen, @Nullable Intermediary intermediary, @Nullable String installDir, boolean generateProfile, boolean copyProfilePath) { + return new InstallClient(minecraftVersion, launcherType, loaderType, loaderVersion, intermediaryGen, intermediary, installDir, generateProfile, copyProfilePath); } - public static InstallServer installServer(String minecraftVersion, LoaderType loaderType, @Nullable String loaderVersion, String installDir, boolean createScripts, boolean installServer) { - return new InstallServer(minecraftVersion, loaderType, loaderVersion, installDir, createScripts, installServer); + public static InstallServer installServer(String minecraftVersion, LoaderType loaderType, @Nullable String loaderVersion, int intermediaryGen, @Nullable Intermediary intermediary, String installDir, boolean createScripts, boolean installServer) { + return new InstallServer(minecraftVersion, loaderType, loaderVersion, intermediaryGen, intermediary, installDir, createScripts, installServer); } static void println(String message) { diff --git a/src/main/java/org/quiltmc/installer/action/InstallClient.java b/src/main/java/org/quiltmc/installer/action/InstallClient.java index 7070b50..ab0cf2b 100644 --- a/src/main/java/org/quiltmc/installer/action/InstallClient.java +++ b/src/main/java/org/quiltmc/installer/action/InstallClient.java @@ -28,7 +28,6 @@ import java.nio.file.StandardOpenOption; import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.function.Consumer; import org.jetbrains.annotations.Nullable; @@ -36,6 +35,7 @@ import org.quiltmc.parsers.json.JsonReader; import org.quiltmc.installer.GameSide; import org.quiltmc.installer.Gsons; +import org.quiltmc.installer.Intermediary; import org.quiltmc.installer.LaunchJson; import org.quiltmc.installer.LauncherProfiles; import org.quiltmc.installer.LauncherType; @@ -51,17 +51,21 @@ public final class InstallClient extends Action { private final LoaderType loaderType; @Nullable private final String loaderVersion; - private final String intermediary; + private final int intermediaryGen; + @Nullable + private final Intermediary intermediary; + @Nullable private final String installDir; private final boolean generateProfile; private final boolean copyProfilePath; private Path installDirPath; - InstallClient(String minecraftVersion, LauncherType launcherType, LoaderType loaderType, @Nullable String loaderVersion, String intermediary, String installDir, boolean generateProfile, boolean copyProfilePath) { + InstallClient(String minecraftVersion, LauncherType launcherType, LoaderType loaderType, @Nullable String loaderVersion, int intermediaryGen, @Nullable Intermediary intermediary, @Nullable String installDir, boolean generateProfile, boolean copyProfilePath) { this.minecraftVersion = minecraftVersion; this.launcherType = launcherType; this.loaderType = loaderType; this.loaderVersion = loaderVersion; + this.intermediaryGen = intermediaryGen; this.intermediary = intermediary; this.installDir = installDir; this.generateProfile = generateProfile; @@ -112,9 +116,9 @@ private void installOfficial(Consumer statusTracker) { * 7. (Optional) create profile if needed */ - CompletableFuture installationInfoFuture = MinecraftInstallation.getInfo(GameSide.CLIENT, this.minecraftVersion, this.loaderType, this.loaderVersion); + CompletableFuture installationInfoFuture = MinecraftInstallation.getInfo(GameSide.CLIENT, this.minecraftVersion, this.loaderType, this.loaderVersion, this.intermediaryGen, this.intermediary); - installationInfoFuture.thenCompose(installationInfo -> LaunchJson.get(installationInfo.manifest().getVersion(this.minecraftVersion)).thenCompose(vanillaLaunchJson -> LaunchJson.get(GameSide.CLIENT, installationInfo.manifest().getVersion(this.minecraftVersion), this.loaderType, installationInfo.loaderVersion()).thenAccept(launchJson -> { + installationInfoFuture.thenCompose(installationInfo -> LaunchJson.get(installationInfo.manifest().getVersion(this.minecraftVersion)).thenCompose(vanillaLaunchJson -> LaunchJson.get(GameSide.CLIENT, installationInfo.manifest().getVersion(this.minecraftVersion), installationInfo.intermediaryGen(), installationInfo.intermediary(), this.loaderType, installationInfo.loaderVersion()).thenAccept(launchJson -> { println("Creating profile launch json"); Map vanillaLaunchJsonMap; @@ -167,7 +171,7 @@ private void installOfficial(Consumer statusTracker) { if (this.generateProfile) { try { println("Creating new profile"); - LauncherProfiles.updateProfiles(this.installDirPath, profileName, this.minecraftVersion, loaderType); + LauncherProfiles.updateProfiles(this.installDirPath, profileName, this.minecraftVersion, this.loaderType, this.intermediaryGen); } catch (IOException e) { throw new UncheckedIOException(e); // Handle via exceptionally } @@ -214,15 +218,16 @@ private static void writeLaunchJson(Path path, String json) { } private void installMultimc(Consumer statusTracker) { - CompletableFuture installationInfoFuture = MinecraftInstallation.getInfo(GameSide.CLIENT, this.minecraftVersion, this.loaderType, this.loaderVersion); + CompletableFuture installationInfoFuture = MinecraftInstallation.getInfo(GameSide.CLIENT, this.minecraftVersion, this.loaderType, this.loaderVersion, this.intermediaryGen, this.intermediary); installationInfoFuture.thenAccept(installationInfo -> { MmcPackCreator.compileMmcZip( Paths.get(this.installDir), this.minecraftVersion, this.loaderType, - this.loaderVersion, - this.intermediary, + installationInfo.loaderVersion(), + installationInfo.intermediaryGen(), + installationInfo.intermediary(), installationInfo.manifest(), this.copyProfilePath ); diff --git a/src/main/java/org/quiltmc/installer/action/InstallServer.java b/src/main/java/org/quiltmc/installer/action/InstallServer.java index 2a6b1fc..9a5b90c 100644 --- a/src/main/java/org/quiltmc/installer/action/InstallServer.java +++ b/src/main/java/org/quiltmc/installer/action/InstallServer.java @@ -53,6 +53,7 @@ import org.quiltmc.installer.Connections; import org.quiltmc.installer.GameSide; import org.quiltmc.installer.Gsons; +import org.quiltmc.installer.Intermediary; import org.quiltmc.installer.LaunchJson; import org.quiltmc.installer.LoaderType; import org.quiltmc.installer.Value; @@ -69,16 +70,21 @@ public final class InstallServer extends Action { private final LoaderType loaderType; @Nullable private final String loaderVersion; + private final int intermediaryGen; + @Nullable + private final Intermediary intermediary; private final String installDir; private final boolean createScripts; private final boolean installServer; private MinecraftInstallation.InstallationInfo installationInfo; private Path installedDir; - InstallServer(String minecraftVersion, LoaderType loaderType, @Nullable String loaderVersion, String installDir, boolean createScripts, boolean installServer) { + InstallServer(String minecraftVersion, LoaderType loaderType, @Nullable String loaderVersion, int intermediaryGen, @Nullable Intermediary intermediary, String installDir, boolean createScripts, boolean installServer) { this.minecraftVersion = minecraftVersion; this.loaderType = loaderType; this.loaderVersion = loaderVersion; + this.intermediaryGen = intermediaryGen; + this.intermediary = intermediary; this.installDir = installDir; this.createScripts = createScripts; this.installServer = installServer; @@ -105,11 +111,11 @@ public void run(Consumer statusTracker) { println(String.format("Installing server launcher for %s with loader %s", this.minecraftVersion, this.loaderVersion)); } - CompletableFuture installationInfoFuture = MinecraftInstallation.getInfo(GameSide.SERVER, this.minecraftVersion, this.loaderType, this.loaderVersion); + CompletableFuture installationInfoFuture = MinecraftInstallation.getInfo(GameSide.SERVER, this.minecraftVersion, this.loaderType, this.loaderVersion, this.intermediaryGen, this.intermediary); installationInfoFuture.thenCompose(installationInfo -> { this.installationInfo = installationInfo; - return LaunchJson.get(GameSide.SERVER, installationInfo.manifest().getVersion(this.minecraftVersion), this.loaderType, installationInfo.loaderVersion()); + return LaunchJson.get(GameSide.SERVER, installationInfo.manifest().getVersion(this.minecraftVersion), installationInfo.intermediaryGen(), installationInfo.intermediary(), this.loaderType, installationInfo.loaderVersion()); }).thenCompose(launchJson -> { println("Installing libraries"); @@ -229,12 +235,9 @@ public static CompletableFuture downloadServer(Path installDir, String min return CompletableFuture.supplyAsync(() -> { // Get the info from the manifest VersionManifest.Version version = info.manifest().getVersion(minecraftVersion); - // Not gonna be null cause we already validated this - @SuppressWarnings("ConstantConditions") - String rawUrl = version.url(); try { - URL url = new URL(rawUrl); + URL url = new URL(version.url()); URLConnection connection = Connections.openConnection(url); InputStreamReader stream = new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8); diff --git a/src/main/java/org/quiltmc/installer/action/ListVersions.java b/src/main/java/org/quiltmc/installer/action/ListVersions.java index 2c9a71a..b06abc9 100644 --- a/src/main/java/org/quiltmc/installer/action/ListVersions.java +++ b/src/main/java/org/quiltmc/installer/action/ListVersions.java @@ -35,25 +35,27 @@ */ public final class ListVersions extends Action { private final LoaderType loaderType; + private final int intermediaryGen; /** * Whether to display snapshot Minecraft versions. */ private final boolean minecraftSnapshots; private final boolean loaderBetas; - ListVersions(LoaderType loaderType, boolean minecraftSnapshots, boolean loaderBetas) { + ListVersions(LoaderType loaderType, int intermediaryGen, boolean minecraftSnapshots, boolean loaderBetas) { this.loaderType = loaderType; + this.intermediaryGen = intermediaryGen; this.minecraftSnapshots = minecraftSnapshots; this.loaderBetas = loaderBetas; } @Override public void run(Consumer statusTracker) { - CompletableFuture versionManifest = VersionManifest.create() + CompletableFuture versionManifest = VersionManifest.create(this.intermediaryGen) .thenAccept(this::displayMinecraftVerions) .exceptionally(this::handleMinecraftVersionExceptions); - CompletableFuture quiltMeta = OrnitheMeta.create(OrnitheMeta.ORNITHE_META_URL, Collections.singleton(OrnitheMeta.loaderVersionsEndpoint(this.loaderType))) + CompletableFuture quiltMeta = OrnitheMeta.create(OrnitheMeta.ORNITHE_META_URL, Collections.singleton(OrnitheMeta.loaderVersionsEndpoint(this.intermediaryGen, this.loaderType))) .thenAccept(this::displayLoaderVersions) .exceptionally(e -> { e.printStackTrace(); @@ -75,7 +77,7 @@ private void displayMinecraftVerions(VersionManifest manifest) { } private void displayLoaderVersions(OrnitheMeta meta) { - List endpoint = meta.getEndpoint(OrnitheMeta.loaderVersionsEndpoint(this.loaderType)); + List endpoint = meta.getEndpoint(OrnitheMeta.loaderVersionsEndpoint(this.intermediaryGen, this.loaderType)); println(Localization.createFrom("cli.latest.loader.release", endpoint.stream().filter(version -> !version.contains("-")).findFirst().get())); if (this.loaderBetas) { diff --git a/src/main/java/org/quiltmc/installer/action/MinecraftInstallation.java b/src/main/java/org/quiltmc/installer/action/MinecraftInstallation.java index 6a5d749..6610e58 100644 --- a/src/main/java/org/quiltmc/installer/action/MinecraftInstallation.java +++ b/src/main/java/org/quiltmc/installer/action/MinecraftInstallation.java @@ -18,13 +18,13 @@ import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import org.jetbrains.annotations.Nullable; import org.quiltmc.installer.GameSide; +import org.quiltmc.installer.Intermediary; import org.quiltmc.installer.LoaderType; import org.quiltmc.installer.OrnitheMeta; import org.quiltmc.installer.VersionManifest; @@ -38,8 +38,8 @@ public final class MinecraftInstallation { * @param loaderVersion the override for the loader version to use * @return a future containing the loader version to use */ - public static CompletableFuture getInfo(GameSide side, String gameVersion, LoaderType loaderType, @Nullable String loaderVersion) { - CompletableFuture versionManifest = VersionManifest.create().thenApply(manifest -> { + public static CompletableFuture getInfo(GameSide side, String gameVersion, LoaderType loaderType, @Nullable String loaderVersion, int intermediaryGen, @Nullable Intermediary intermediaryVersion) { + CompletableFuture versionManifest = VersionManifest.create(intermediaryGen).thenApply(manifest -> { if (manifest.getVersion(gameVersion) != null) { return manifest; } @@ -48,26 +48,30 @@ public static CompletableFuture getInfo(GameSide side, String }); Set> endpoints = new HashSet<>(); - endpoints.add(OrnitheMeta.loaderVersionsEndpoint(loaderType)); - endpoints.add(OrnitheMeta.INTERMEDIARY_VERSIONS_ENDPOINT); + endpoints.add(OrnitheMeta.loaderVersionsEndpoint(intermediaryGen, loaderType)); + endpoints.add(OrnitheMeta.intermediaryVersionsEndpoint(intermediaryGen)); CompletableFuture metaFuture = OrnitheMeta.create(OrnitheMeta.ORNITHE_META_URL, endpoints); // Verify we actually have intermediary for the specified version - CompletableFuture intermediary = versionManifest.thenCompose(manifest -> { - VersionManifest.Version version = manifest.getVersion(gameVersion); + CompletableFuture intermediary = metaFuture.thenApply(meta -> { + if (intermediaryVersion != null) { + return intermediaryVersion; + } - return metaFuture.thenAccept(meta -> { - Map intermediaryVersions = meta.getEndpoint(OrnitheMeta.INTERMEDIARY_VERSIONS_ENDPOINT); + List intermediaryVersions = meta.getEndpoint(OrnitheMeta.intermediaryVersionsEndpoint(intermediaryGen)); - if (intermediaryVersions.get(version.id(side)) == null) { - throw new IllegalArgumentException(String.format("Minecraft version %s exists but has no intermediary", gameVersion)); + for (Intermediary iv : intermediaryVersions) { + if (side.versionMatches(iv.getVersion()) && gameVersion.equals(side.stripFromVersion(iv.getVersion()))) { + return iv; } - }); + } + + throw new IllegalArgumentException(String.format("Minecraft version %s exists but has no intermediary", gameVersion)); }); CompletableFuture loaderVersionFuture = metaFuture.thenApply(meta -> { - List versions = meta.getEndpoint(OrnitheMeta.loaderVersionsEndpoint(loaderType)); + List versions = meta.getEndpoint(OrnitheMeta.loaderVersionsEndpoint(intermediaryGen, loaderType)); if (loaderVersion != null) { if (!versions.contains(loaderVersion)) { @@ -87,7 +91,7 @@ public static CompletableFuture getInfo(GameSide side, String return CompletableFuture.allOf(versionManifest, intermediary, loaderVersionFuture).thenApply(_v -> { try { - return new InstallationInfo(loaderVersionFuture.get(), versionManifest.get()); + return new InstallationInfo(loaderVersionFuture.get(), intermediaryGen, intermediary.get(), versionManifest.get()); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } @@ -99,10 +103,14 @@ private MinecraftInstallation() { public static final class InstallationInfo { private final String loaderVersion; + private final int intermediaryGen; + private final Intermediary intermediary; private final VersionManifest manifest; - InstallationInfo(String loaderVersion, VersionManifest manifest) { + InstallationInfo(String loaderVersion, int intermediaryGen, Intermediary intermediary, VersionManifest manifest) { this.loaderVersion = loaderVersion; + this.intermediaryGen = intermediaryGen; + this.intermediary = intermediary; this.manifest = manifest; } @@ -110,6 +118,14 @@ public String loaderVersion() { return this.loaderVersion; } + public int intermediaryGen() { + return this.intermediaryGen; + } + + public Intermediary intermediary() { + return this.intermediary; + } + public VersionManifest manifest() { return this.manifest; } diff --git a/src/main/java/org/quiltmc/installer/gui/swing/AbstractPanel.java b/src/main/java/org/quiltmc/installer/gui/swing/AbstractPanel.java index 1e1e5fd..904c77d 100644 --- a/src/main/java/org/quiltmc/installer/gui/swing/AbstractPanel.java +++ b/src/main/java/org/quiltmc/installer/gui/swing/AbstractPanel.java @@ -18,9 +18,10 @@ import java.awt.*; import java.io.File; -import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import javax.swing.BoxLayout; import javax.swing.JComboBox; @@ -34,6 +35,7 @@ import org.jetbrains.annotations.Nullable; import org.quiltmc.installer.GameSide; +import org.quiltmc.installer.Intermediary; import org.quiltmc.installer.LoaderType; import org.quiltmc.installer.Localization; import org.quiltmc.installer.VersionManifest; @@ -46,7 +48,7 @@ abstract class AbstractPanel extends JPanel { @Nullable private Map> loaderVersions; @Nullable - private Map intermediaryVersions; + private Map intermediaryVersions; AbstractPanel(SwingInstaller gui) { this.gui = gui; @@ -60,10 +62,12 @@ JComponent addRow() { return rowPanel; } - void receiveVersions(VersionManifest manifest, Map> loaderVersions, Map intermediaryVersions) { + void receiveVersions(GameSide side, VersionManifest manifest, Map> loaderVersions, List intermediaryVersions) { this.manifest = manifest; this.loaderVersions = loaderVersions; - this.intermediaryVersions = intermediaryVersions; + this.intermediaryVersions = intermediaryVersions.stream() + .filter(intermediary -> side.versionMatches(intermediary.getVersion())) + .collect(Collectors.toMap(intermediary -> side.stripFromVersion(intermediary.getVersion()), Function.identity())); } @Nullable @@ -82,13 +86,13 @@ public List loaderVersions(LoaderType type) { } @Nullable - public Map intermediaryVersions() { + public Map intermediaryVersions() { return this.intermediaryVersions; } abstract LoaderType loaderType(); - static void populateMinecraftVersions(GameSide side, JComboBox comboBox, VersionManifest manifest, Map intermediaryVersions, boolean snapshots) { + static void populateMinecraftVersions(GameSide side, JComboBox comboBox, VersionManifest manifest, Map intermediaryVersions, boolean snapshots) { // Setup the combo box for Minecraft version selection comboBox.removeAllItems(); @@ -98,8 +102,9 @@ static void populateMinecraftVersions(GameSide side, JComboBox comboBox, || (version.type().equals("old_alpha") && snapshots) || (version.type().equals("alpha_server") && snapshots) || (version.type().equals("classic_server") && snapshots)) - .filter(version -> intermediaryVersions.containsKey(version.id()) || intermediaryVersions.containsKey(version.id() + "-" + side.id())) - .map(VersionManifest.Version::id).forEachOrdered(comboBox::addItem); + .map(VersionManifest.Version::id) + .filter(intermediaryVersions::containsKey) + .forEachOrdered(comboBox::addItem); comboBox.setEnabled(true); } diff --git a/src/main/java/org/quiltmc/installer/gui/swing/ClientPanel.java b/src/main/java/org/quiltmc/installer/gui/swing/ClientPanel.java index 78e4b5e..64a1e0d 100644 --- a/src/main/java/org/quiltmc/installer/gui/swing/ClientPanel.java +++ b/src/main/java/org/quiltmc/installer/gui/swing/ClientPanel.java @@ -212,14 +212,14 @@ private void install(ActionEvent event) { String loaderVersion = (String) this.loaderVersionSelector.getSelectedItem(); LauncherType launcherType = this.launcherType(); LoaderType loaderType = this.loaderType(); - VersionManifest.Version version = this.manifest().getVersion(minecraftVersion); Action action = Action.installClient( minecraftVersion, launcherType, loaderType, loaderVersion, - this.intermediaryVersions().get(version.id(GameSide.CLIENT)), + -1, + this.intermediaryVersions().get(minecraftVersion), this.installLocation.getText(), this.generateProfile, this.copyProfilePath @@ -259,11 +259,10 @@ LoaderType loaderType() { return ((LoaderLabel) this.loaderTypeSelector.getSelectedItem()).type; } - @Override - void receiveVersions(VersionManifest manifest, Map> loaderVersions, Map intermediaryVersions) { - super.receiveVersions(manifest, loaderVersions, intermediaryVersions); + void receiveVersions(VersionManifest manifest, Map> loaderVersions, List intermediaryVersions) { + super.receiveVersions(GameSide.CLIENT, manifest, loaderVersions, intermediaryVersions); - populateMinecraftVersions(GameSide.CLIENT, this.minecraftVersionSelector, manifest, intermediaryVersions, this.showSnapshots); + populateMinecraftVersions(GameSide.CLIENT, this.minecraftVersionSelector, this.manifest(), this.intermediaryVersions(), this.showSnapshots); this.showSnapshotsCheckBox.setEnabled(true); populateLoaderVersions(GameSide.CLIENT, this.loaderVersionSelector, this.loaderVersions(this.loaderType()), this.showLoaderBetas); this.showLoaderBetasCheckBox.setEnabled(true); diff --git a/src/main/java/org/quiltmc/installer/gui/swing/ServerPanel.java b/src/main/java/org/quiltmc/installer/gui/swing/ServerPanel.java index 7e730e6..49d1cc6 100644 --- a/src/main/java/org/quiltmc/installer/gui/swing/ServerPanel.java +++ b/src/main/java/org/quiltmc/installer/gui/swing/ServerPanel.java @@ -31,6 +31,7 @@ import org.jetbrains.annotations.Nullable; import org.quiltmc.installer.GameSide; import org.quiltmc.installer.Gsons; +import org.quiltmc.installer.Intermediary; import org.quiltmc.installer.LoaderType; import org.quiltmc.installer.Localization; import org.quiltmc.installer.VersionManifest; @@ -178,11 +179,10 @@ LoaderType loaderType() { return ((LoaderLabel) this.loaderTypeSelector.getSelectedItem()).type; } - @Override - void receiveVersions(VersionManifest manifest, Map> loaderVersions, Map intermediaryVersions) { - super.receiveVersions(manifest, loaderVersions, intermediaryVersions); + void receiveVersions(VersionManifest manifest, Map> loaderVersions, List intermediaryVersions) { + super.receiveVersions(GameSide.SERVER, manifest, loaderVersions, intermediaryVersions); - populateMinecraftVersions(GameSide.SERVER, this.minecraftVersionSelector, manifest, intermediaryVersions, this.showSnapshots); + populateMinecraftVersions(GameSide.SERVER, this.minecraftVersionSelector, this.manifest(), this.intermediaryVersions(), this.showSnapshots); updateFlags(); this.showSnapshotsCheckBox.setEnabled(true); populateLoaderVersions(GameSide.SERVER, this.loaderVersionSelector, this.loaderVersions(this.loaderType()), this.showLoaderBetas); @@ -215,12 +215,16 @@ private void install(ActionEvent event) { return; } + String minecraftVersion = (String) this.minecraftVersionSelector.getSelectedItem(); + String loaderVersion = (String) this.loaderVersionSelector.getSelectedItem(); LoaderType loaderType = this.loaderType(); InstallServer action = Action.installServer( - (String) this.minecraftVersionSelector.getSelectedItem(), + minecraftVersion, loaderType, - (String) this.loaderVersionSelector.getSelectedItem(), + loaderVersion, + -1, + this.intermediaryVersions().get(minecraftVersion), this.installLocation.getText(), this.generateLaunchScripts, this.downloadServer diff --git a/src/main/java/org/quiltmc/installer/gui/swing/SwingInstaller.java b/src/main/java/org/quiltmc/installer/gui/swing/SwingInstaller.java index f7227e8..6426793 100644 --- a/src/main/java/org/quiltmc/installer/gui/swing/SwingInstaller.java +++ b/src/main/java/org/quiltmc/installer/gui/swing/SwingInstaller.java @@ -16,6 +16,7 @@ package org.quiltmc.installer.gui.swing; +import org.quiltmc.installer.Intermediary; import org.quiltmc.installer.LoaderType; import org.quiltmc.installer.Localization; import org.quiltmc.installer.OrnitheMeta; @@ -56,14 +57,14 @@ private SwingInstaller() { // Lookup loader and intermediary Set> endpoints = new HashSet<>(); for (LoaderType type : LoaderType.values()) { - endpoints.add(OrnitheMeta.loaderVersionsEndpoint(type)); + endpoints.add(OrnitheMeta.loaderVersionsEndpoint(-1, type)); } - endpoints.add(OrnitheMeta.INTERMEDIARY_VERSIONS_ENDPOINT); + endpoints.add(OrnitheMeta.intermediaryVersionsEndpoint(-1)); - OrnitheMeta.create(OrnitheMeta.ORNITHE_META_URL, endpoints).thenAcceptBothAsync(VersionManifest.create(), ((quiltMeta, manifest) -> { + OrnitheMeta.create(OrnitheMeta.ORNITHE_META_URL, endpoints).thenAcceptBothAsync(VersionManifest.create(-1), ((quiltMeta, manifest) -> { Map> loaderVersions = new EnumMap<>(LoaderType.class); for (LoaderType type : LoaderType.values()) { - loaderVersions.put(type, quiltMeta.getEndpoint(OrnitheMeta.loaderVersionsEndpoint(type)).stream().filter(v -> { + loaderVersions.put(type, quiltMeta.getEndpoint(OrnitheMeta.loaderVersionsEndpoint(-1, type)).stream().filter(v -> { if (type != LoaderType.QUILT) { return true; } @@ -72,7 +73,7 @@ private SwingInstaller() { return !(v.startsWith("0.16.0-beta.") && v.length() == 13 && v.charAt(12) != '9'); }).collect(Collectors.toList())); } - Map intermediaryVersions = quiltMeta.getEndpoint(OrnitheMeta.INTERMEDIARY_VERSIONS_ENDPOINT); + List intermediaryVersions = quiltMeta.getEndpoint(OrnitheMeta.intermediaryVersionsEndpoint(-1)); this.clientPanel.receiveVersions(manifest, loaderVersions, intermediaryVersions); this.serverPanel.receiveVersions(manifest, loaderVersions, intermediaryVersions);