From a1cdbe042ed08c24761426a61002e3a22cab3d5e Mon Sep 17 00:00:00 2001 From: Henry Le Grys Date: Mon, 28 Apr 2025 11:23:05 +0100 Subject: [PATCH 1/3] Update to gradle 8.14 & fix build issues --- build.gradle | 39 +++--------------------- creator-tools/build.gradle | 10 +----- gradle/wrapper/gradle-wrapper.properties | 2 +- launcher-bootstrap/build.gradle | 10 +----- launcher-builder/build.gradle | 11 +------ launcher-fancy/build.gradle | 13 +------- launcher/build.gradle | 10 +----- 7 files changed, 10 insertions(+), 85 deletions(-) diff --git a/build.gradle b/build.gradle index 49eea452c..c72847f66 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { - id "com.github.johnrengelman.shadow" version "7.1.2" - id 'io.freefair.lombok' version '5.3.0' + id "com.gradleup.shadow" version "8.3.6" + id 'io.freefair.lombok' version '8.13.1' } println """ @@ -39,37 +39,6 @@ subprojects { workingDir.mkdirs() } - // Work around gradle shadow bug - // see https://github.com/johnrengelman/shadow/issues/713 - afterEvaluate { - startScripts { - dependsOn(shadowJar) - } - - distTar { - dependsOn(shadowJar) - } - - distZip { - dependsOn(shadowJar) - } - - startShadowScripts { - dependsOn(jar) - } - - shadowDistTar { - dependsOn(jar) - } - - shadowDistZip { - dependsOn(jar) - } - } -} - -task clean { - subprojects { - rootProject.clean.dependsOn tasks.matching { it.name == "clean" } - } + // Disable un-shadowed jar + tasks.jar.enabled = false } diff --git a/creator-tools/build.gradle b/creator-tools/build.gradle index 0905e6d0b..1179603cb 100644 --- a/creator-tools/build.gradle +++ b/creator-tools/build.gradle @@ -1,6 +1,6 @@ plugins { id 'application' - id "com.github.johnrengelman.shadow" + id 'com.gradleup.shadow' id 'io.freefair.lombok' } @@ -23,11 +23,3 @@ processResources { } } } - -shadowJar { - archiveClassifier.set("") -} - -build { - dependsOn(shadowJar) -} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fcea..5c82cb032 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/launcher-bootstrap/build.gradle b/launcher-bootstrap/build.gradle index 12969d60d..313b01962 100644 --- a/launcher-bootstrap/build.gradle +++ b/launcher-bootstrap/build.gradle @@ -1,6 +1,6 @@ plugins { id 'application' - id "com.github.johnrengelman.shadow" + id 'com.gradleup.shadow' id 'io.freefair.lombok' } @@ -20,11 +20,3 @@ processResources { } } } - -shadowJar { - archiveClassifier.set("") -} - -build { - dependsOn(shadowJar) -} diff --git a/launcher-builder/build.gradle b/launcher-builder/build.gradle index 224895771..8e5b5bf10 100644 --- a/launcher-builder/build.gradle +++ b/launcher-builder/build.gradle @@ -1,7 +1,7 @@ plugins { id 'application' id 'java-library' - id "com.github.johnrengelman.shadow" + id 'com.gradleup.shadow' id 'io.freefair.lombok' } @@ -16,13 +16,4 @@ dependencies { shadowJar { dependsOn ':launcher:shadowJar' - archiveClassifier.set("") } - -// Work around gradle shadow bug -// see https://github.com/johnrengelman/shadow/issues/713 -startScripts.dependsOn(':launcher:shadowJar') -distTar.dependsOn(':launcher:shadowJar') -distZip.dependsOn(':launcher:shadowJar') - -build.dependsOn(shadowJar) diff --git a/launcher-fancy/build.gradle b/launcher-fancy/build.gradle index 22af7053d..36a406123 100644 --- a/launcher-fancy/build.gradle +++ b/launcher-fancy/build.gradle @@ -1,6 +1,6 @@ plugins { id 'application' - id "com.github.johnrengelman.shadow" + id 'com.gradleup.shadow' id 'io.freefair.lombok' } @@ -22,15 +22,4 @@ dependencies { shadowJar { dependsOn ':launcher:shadowJar' - archiveClassifier.set("") -} - -// Work around gradle shadow bug -// see https://github.com/johnrengelman/shadow/issues/713 -startScripts.dependsOn(':launcher:shadowJar') -distTar.dependsOn(':launcher:shadowJar') -distZip.dependsOn(':launcher:shadowJar') - -build { - dependsOn(shadowJar) } diff --git a/launcher/build.gradle b/launcher/build.gradle index b41115ad2..afcdf3ae6 100644 --- a/launcher/build.gradle +++ b/launcher/build.gradle @@ -1,7 +1,7 @@ plugins { id 'application' id 'java-library' - id "com.github.johnrengelman.shadow" + id 'com.gradleup.shadow' id 'io.freefair.lombok' } @@ -30,11 +30,3 @@ processResources { } } } - -shadowJar { - archiveClassifier.set("") -} - -build { - dependsOn(shadowJar) -} From 6484d13113794079300c4e8f9c7151d24fdd691b Mon Sep 17 00:00:00 2001 From: Henry Le Grys Date: Wed, 1 Oct 2025 10:30:42 +0100 Subject: [PATCH 2/3] Hide "Add Mojang account" button by default Doesn't work anymore and was just contributing to confusion. --- build.gradle | 3 --- .../com/skcraft/launcher/dialog/AccountSelectDialog.java | 5 ++++- .../main/resources/com/skcraft/launcher/launcher.properties | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index c72847f66..6a3fe42df 100644 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,4 @@ subprojects { workingDir = new File(rootDir, "run/") workingDir.mkdirs() } - - // Disable un-shadowed jar - tasks.jar.enabled = false } diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/AccountSelectDialog.java b/launcher/src/main/java/com/skcraft/launcher/dialog/AccountSelectDialog.java index 89d0785f2..48a52d601 100644 --- a/launcher/src/main/java/com/skcraft/launcher/dialog/AccountSelectDialog.java +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/AccountSelectDialog.java @@ -76,7 +76,10 @@ private void initComponents() { addMojangButton.setAlignmentX(CENTER_ALIGNMENT); addMicrosoftButton.setAlignmentX(CENTER_ALIGNMENT); removeSelected.setAlignmentX(CENTER_ALIGNMENT); - loginButtonsRow.add(addMojangButton, BorderLayout.NORTH); + + if (launcher.getProperties().containsKey("yggdrasilAuthUrl")) { + loginButtonsRow.add(addMojangButton, BorderLayout.NORTH); + } loginButtonsRow.add(addMicrosoftButton, BorderLayout.CENTER); loginButtonsRow.add(removeSelected, BorderLayout.SOUTH); loginButtonsRow.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); diff --git a/launcher/src/main/resources/com/skcraft/launcher/launcher.properties b/launcher/src/main/resources/com/skcraft/launcher/launcher.properties index 1ff0c13cd..6100300cb 100644 --- a/launcher/src/main/resources/com/skcraft/launcher/launcher.properties +++ b/launcher/src/main/resources/com/skcraft/launcher/launcher.properties @@ -16,7 +16,6 @@ offlinePlayerName=Player versionManifestUrl=https://launchermeta.mojang.com/mc/game/version_manifest.json librariesSource=https://libraries.minecraft.net/ assetsSource=https://resources.download.minecraft.net/ -yggdrasilAuthUrl=https://authserver.mojang.com/authenticate microsoftClientId=d18bb4d8-a27f-4451-a87f-fe6de4436813 resetPasswordUrl=https://minecraft.net/resetpassword From 1aa968f13f637a3106ab3a0a6a820652bd4347a4 Mon Sep 17 00:00:00 2001 From: Henry Le Grys Date: Wed, 26 Nov 2025 14:32:59 +0000 Subject: [PATCH 3/3] Add automatic download & setup of Java runtimes --- .../java/com/skcraft/launcher/Launcher.java | 4 + .../launcher/install/CreateFolder.java | 28 ++++ .../skcraft/launcher/install/CreateLink.java | 31 +++++ .../launcher/install/FileSetExecutable.java | 28 ++++ .../skcraft/launcher/install/FileVerify.java | 4 +- .../com/skcraft/launcher/launch/Runner.java | 18 +-- .../model/minecraft/runtime/DownloadInfo.java | 16 +++ .../model/minecraft/runtime/Format.java | 8 ++ .../model/minecraft/runtime/RuntimeInfo.java | 24 ++++ .../model/minecraft/runtime/RuntimeList.java | 25 ++++ .../minecraft/runtime/RuntimeManifest.java | 16 +++ .../runtime/RuntimeManifestEntry.java | 45 +++++++ .../minecraft/runtime/RuntimePlatform.java | 43 ++++++ .../skcraft/launcher/update/BaseUpdater.java | 5 + .../com/skcraft/launcher/update/Updater.java | 6 + .../update/runtime/JavaRuntimeManager.java | 122 ++++++++++++++++++ .../launcher/update/runtime/RuntimeData.java | 18 +++ .../skcraft/launcher/lang/Launcher.properties | 5 + .../com/skcraft/launcher/launcher.properties | 1 + 19 files changed, 438 insertions(+), 9 deletions(-) create mode 100644 launcher/src/main/java/com/skcraft/launcher/install/CreateFolder.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/install/CreateLink.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/install/FileSetExecutable.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/DownloadInfo.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/Format.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeInfo.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeList.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeManifest.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeManifestEntry.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimePlatform.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/update/runtime/JavaRuntimeManager.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/update/runtime/RuntimeData.java diff --git a/launcher/src/main/java/com/skcraft/launcher/Launcher.java b/launcher/src/main/java/com/skcraft/launcher/Launcher.java index baef3da7e..068addd64 100644 --- a/launcher/src/main/java/com/skcraft/launcher/Launcher.java +++ b/launcher/src/main/java/com/skcraft/launcher/Launcher.java @@ -19,6 +19,7 @@ import com.skcraft.launcher.persistence.Persistence; import com.skcraft.launcher.swing.SwingHelper; import com.skcraft.launcher.update.UpdateManager; +import com.skcraft.launcher.update.runtime.JavaRuntimeManager; import com.skcraft.launcher.util.Environment; import com.skcraft.launcher.util.HttpRequest; import com.skcraft.launcher.util.SharedLocale; @@ -63,6 +64,8 @@ public final class Launcher { @Getter private final Configuration config; @Getter private final AccountList accounts; @Getter private final AssetsRoot assets; + @Getter + private final JavaRuntimeManager runtimeManager; @Getter private final LaunchSupervisor launchSupervisor = new LaunchSupervisor(this); @Getter private final UpdateManager updateManager = new UpdateManager(this); @Getter private final InstanceTasks instanceTasks = new InstanceTasks(this); @@ -93,6 +96,7 @@ public Launcher(@NonNull File baseDir, @NonNull File configDir) throws IOExcepti this.properties = LauncherUtils.loadProperties(Launcher.class, "launcher.properties", "com.skcraft.launcher.propertiesFile"); this.instances = new InstanceList(this); this.assets = new AssetsRoot(new File(baseDir, "assets")); + this.runtimeManager = new JavaRuntimeManager(new File(baseDir, "runtimes")); this.config = Persistence.load(new File(configDir, "config.json"), Configuration.class); this.accounts = Persistence.load(new File(configDir, "accounts.dat"), AccountList.class); diff --git a/launcher/src/main/java/com/skcraft/launcher/install/CreateFolder.java b/launcher/src/main/java/com/skcraft/launcher/install/CreateFolder.java new file mode 100644 index 000000000..8aa13cfea --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/CreateFolder.java @@ -0,0 +1,28 @@ +package com.skcraft.launcher.install; + +import com.skcraft.launcher.Launcher; +import lombok.RequiredArgsConstructor; + +import java.io.File; + +import static com.skcraft.launcher.util.SharedLocale.tr; + +@RequiredArgsConstructor +public class CreateFolder implements InstallTask { + private final File target; + + @Override + public void execute(Launcher launcher) throws Exception { + target.mkdirs(); + } + + @Override + public double getProgress() { + return -1; + } + + @Override + public String getStatus() { + return tr("installer.creatingDirectory", target); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/install/CreateLink.java b/launcher/src/main/java/com/skcraft/launcher/install/CreateLink.java new file mode 100644 index 000000000..09e0e77d2 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/CreateLink.java @@ -0,0 +1,31 @@ +package com.skcraft.launcher.install; + +import com.skcraft.launcher.Launcher; +import lombok.RequiredArgsConstructor; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; + +import static com.skcraft.launcher.util.SharedLocale.tr; + +@RequiredArgsConstructor +public class CreateLink implements InstallTask { + private final File target; + private final Path existing; + + @Override + public void execute(Launcher launcher) throws Exception { + Files.createSymbolicLink(target.toPath(), existing); + } + + @Override + public double getProgress() { + return -1; + } + + @Override + public String getStatus() { + return tr("installer.creatingLink", target, existing); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/install/FileSetExecutable.java b/launcher/src/main/java/com/skcraft/launcher/install/FileSetExecutable.java new file mode 100644 index 000000000..88ce70da6 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/FileSetExecutable.java @@ -0,0 +1,28 @@ +package com.skcraft.launcher.install; + +import com.skcraft.launcher.Launcher; +import lombok.RequiredArgsConstructor; + +import java.io.File; + +import static com.skcraft.launcher.util.SharedLocale.tr; + +@RequiredArgsConstructor +public class FileSetExecutable implements InstallTask { + private final File target; + + @Override + public void execute(Launcher launcher) throws Exception { + target.setExecutable(true, false); + } + + @Override + public double getProgress() { + return -1; + } + + @Override + public String getStatus() { + return tr("installer.settingExecutable", target); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/install/FileVerify.java b/launcher/src/main/java/com/skcraft/launcher/install/FileVerify.java index d8a313ad4..056df26f4 100644 --- a/launcher/src/main/java/com/skcraft/launcher/install/FileVerify.java +++ b/launcher/src/main/java/com/skcraft/launcher/install/FileVerify.java @@ -8,6 +8,8 @@ import java.io.File; +import static com.skcraft.launcher.util.SharedLocale.tr; + @RequiredArgsConstructor @Log public class FileVerify implements InstallTask { @@ -37,6 +39,6 @@ public double getProgress() { @Override public String getStatus() { - return "Verifying " + name; + return tr("installer.verifyingFile", name); } } diff --git a/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java b/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java index 7846861f8..c48f5a6a4 100644 --- a/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java +++ b/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java @@ -17,7 +17,6 @@ import com.skcraft.launcher.auth.Session; import com.skcraft.launcher.install.ZipExtract; import com.skcraft.launcher.launch.runtime.JavaRuntime; -import com.skcraft.launcher.launch.runtime.JavaRuntimeFinder; import com.skcraft.launcher.model.minecraft.*; import com.skcraft.launcher.persistence.Persistence; import com.skcraft.launcher.util.Environment; @@ -57,7 +56,9 @@ public class Runner implements Callable, ProgressObservable { private final Session session; private final File extractDir; private final BiPredicate javaRuntimeMismatch; - @Getter @Setter private Environment environment = Environment.getInstance(); + @Getter + @Setter + private Environment environment = Environment.getInstance(); private VersionManifest versionManifest; private AssetsIndex assetsIndex; @@ -69,10 +70,11 @@ public class Runner implements Callable, ProgressObservable { /** * Create a new instance launcher. - * @param launcher the launcher - * @param instance the instance - * @param session the session - * @param extractDir the directory to extract to + * + * @param launcher the launcher + * @param instance the instance + * @param session the session + * @param extractDir the directory to extract to * @param javaRuntimeMismatch */ public Runner(@NonNull Launcher launcher, @NonNull Instance instance, @@ -280,7 +282,7 @@ private void addJvmArgs() throws IOException, LauncherException { JavaRuntime selectedRuntime = Optional.ofNullable(instance.getSettings().getRuntime()) .orElseGet(() -> Optional.ofNullable(versionManifest.getJavaVersion()) - .flatMap(JavaRuntimeFinder::findBestJavaRuntime) + .flatMap(launcher.getRuntimeManager()::getRuntime) .orElse(config.getJavaRuntime()) ); @@ -288,7 +290,7 @@ private void addJvmArgs() throws IOException, LauncherException { builder.setRuntime(selectedRuntime); List flags = builder.getFlags(); - String[] rawJvmArgsList = new String[] { + String[] rawJvmArgsList = new String[]{ config.getJvmArgs(), instance.getSettings().getCustomJvmArgs() }; diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/DownloadInfo.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/DownloadInfo.java new file mode 100644 index 000000000..434cd568d --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/DownloadInfo.java @@ -0,0 +1,16 @@ +package com.skcraft.launcher.model.minecraft.runtime; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +@JsonIgnoreProperties(ignoreUnknown = true) +@Data +@Jacksonized +@Builder +public class DownloadInfo { + private final String url; + private final String sha1; + private final long size; +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/Format.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/Format.java new file mode 100644 index 000000000..da751257f --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/Format.java @@ -0,0 +1,8 @@ +package com.skcraft.launcher.model.minecraft.runtime; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public enum Format { + @JsonProperty("raw") RAW, + @JsonProperty("lzma") LZMA; +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeInfo.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeInfo.java new file mode 100644 index 000000000..90e5db053 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeInfo.java @@ -0,0 +1,24 @@ +package com.skcraft.launcher.model.minecraft.runtime; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +@Data +@Jacksonized +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +public class RuntimeInfo { + private final DownloadInfo manifest; + private final Version version; + + @Data + @Jacksonized + @Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Version { + private final String name; + private final String released; + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeList.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeList.java new file mode 100644 index 000000000..46cb9152b --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeList.java @@ -0,0 +1,25 @@ +package com.skcraft.launcher.model.minecraft.runtime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.skcraft.launcher.model.minecraft.JavaVersion; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class RuntimeList { + @JsonValue + private final Map>> runtimesByPlatform; + + @JsonCreator + public RuntimeList(Map>> runtimesByPlatform) { + this.runtimesByPlatform = runtimesByPlatform; + } + + public RuntimeInfo getRuntime(RuntimePlatform platform, JavaVersion target) { + if (platform == null) return null; + return runtimesByPlatform.get(platform).get(target.getComponent()).get(0); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeManifest.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeManifest.java new file mode 100644 index 000000000..d4549f981 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeManifest.java @@ -0,0 +1,16 @@ +package com.skcraft.launcher.model.minecraft.runtime; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +import java.util.Map; + +@Data +@Jacksonized +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +public class RuntimeManifest { + private final Map files; +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeManifestEntry.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeManifestEntry.java new file mode 100644 index 000000000..0e58dc2c9 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimeManifestEntry.java @@ -0,0 +1,45 @@ +package com.skcraft.launcher.model.minecraft.runtime; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.jackson.Jacksonized; + +import java.util.Map; + +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(RuntimeManifestEntry.File.class), + @JsonSubTypes.Type(RuntimeManifestEntry.Directory.class), + @JsonSubTypes.Type(RuntimeManifestEntry.Link.class), +}) +public interface RuntimeManifestEntry { + @Data + @Jacksonized + @Builder + @JsonTypeName("file") + @JsonIgnoreProperties(ignoreUnknown = true) + class File implements RuntimeManifestEntry { + private final Map downloads; + private final boolean executable; + } + + @NoArgsConstructor + @JsonTypeName("directory") + @JsonIgnoreProperties(ignoreUnknown = true) + class Directory implements RuntimeManifestEntry { + } + + @Jacksonized + @Builder + @Data + @JsonTypeName("link") + @JsonIgnoreProperties(ignoreUnknown = true) + class Link implements RuntimeManifestEntry { + private final String target; + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimePlatform.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimePlatform.java new file mode 100644 index 000000000..e5b127993 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/runtime/RuntimePlatform.java @@ -0,0 +1,43 @@ +package com.skcraft.launcher.model.minecraft.runtime; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.skcraft.launcher.util.Environment; +import com.skcraft.launcher.util.Platform; +import lombok.Getter; + +public enum RuntimePlatform { + @JsonProperty("linux") LINUX(true), + @JsonProperty("linux-i386") LINUX_386(false), + @JsonProperty("mac-os") MAC_OS(true), + @JsonProperty("mac-os-arm64") MAC_OS_ARM64(true), + @JsonProperty("windows-arm64") WINDOWS_ARM64(true), + @JsonProperty("windows-x64") WINDOWS_X64(true), + @JsonProperty("windows-x86") WINDOWS_X86(false); + + @Getter + private final boolean is64Bit; + + RuntimePlatform(boolean is64Bit) { + this.is64Bit = is64Bit; + } + + public static RuntimePlatform from(Environment environment) { + Platform platform = environment.getPlatform(); + String arch = environment.getArch(); + + switch (platform) { + case WINDOWS: + if (arch.equals("aarch64")) return WINDOWS_ARM64; + else if (arch.equals("x86")) return WINDOWS_X86; + else return WINDOWS_X64; + case MAC_OS_X: + if (arch.equals("aarch64")) return MAC_OS_ARM64; + else return MAC_OS; + case LINUX: + if (arch.equals("i386")) return LINUX_386; + else return LINUX; + default: + return null; + } + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/update/BaseUpdater.java b/launcher/src/main/java/com/skcraft/launcher/update/BaseUpdater.java index 4bb4a76f2..08651c578 100644 --- a/launcher/src/main/java/com/skcraft/launcher/update/BaseUpdater.java +++ b/launcher/src/main/java/com/skcraft/launcher/update/BaseUpdater.java @@ -309,6 +309,11 @@ protected void installLibraries(@NonNull Installer installer, } } + protected void installRuntime(@NonNull Installer installer, + @NonNull JavaVersion version) throws IOException, InterruptedException { + launcher.getRuntimeManager().install(installer, launcher.propUrl("runtimeManifestUrl"), version); + } + private static void writeDataFile(File path, Object object) { try { Persistence.write(path, object); diff --git a/launcher/src/main/java/com/skcraft/launcher/update/Updater.java b/launcher/src/main/java/com/skcraft/launcher/update/Updater.java index 745c20aab..2b21316d7 100644 --- a/launcher/src/main/java/com/skcraft/launcher/update/Updater.java +++ b/launcher/src/main/java/com/skcraft/launcher/update/Updater.java @@ -174,6 +174,12 @@ protected void update(Instance instance) throws Exception { progress = new DefaultProgress(-1, SharedLocale.tr("instanceUpdater.buildingDownloadList")); + // Install the Java runtime + log.info("Enumerating Java runtime..."); + if (version.getJavaVersion() != null) { + installRuntime(installer, version.getJavaVersion()); + } + // Install the .jar File jarPath = launcher.getJarPath(version); VersionManifest.Artifact clientJar = version.getDownloads().get("client"); diff --git a/launcher/src/main/java/com/skcraft/launcher/update/runtime/JavaRuntimeManager.java b/launcher/src/main/java/com/skcraft/launcher/update/runtime/JavaRuntimeManager.java new file mode 100644 index 000000000..e3f8b1b71 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/update/runtime/JavaRuntimeManager.java @@ -0,0 +1,122 @@ +package com.skcraft.launcher.update.runtime; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.install.*; +import com.skcraft.launcher.launch.runtime.JavaRuntime; +import com.skcraft.launcher.model.minecraft.JavaVersion; +import com.skcraft.launcher.model.minecraft.runtime.*; +import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.util.Environment; +import com.skcraft.launcher.util.HttpRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Path; +import java.util.Optional; + +import static com.skcraft.launcher.util.HttpRequest.url; +import static com.skcraft.launcher.util.SharedLocale.tr; + +@Log +@RequiredArgsConstructor +public class JavaRuntimeManager { + private static final ObjectMapper mapper = new ObjectMapper(); + private final File runtimesDir; + + public Optional getRuntime(JavaVersion version) { + File runtimeDir = new File(runtimesDir, version.getComponent()); + + if (runtimeDir.isDirectory()) { + RuntimeData info = Persistence.read(new File(runtimeDir, "runtime.json"), RuntimeData.class); + + return Optional.of(new JavaRuntime(runtimeDir, info.getVersion(), info.is64Bit())); + } else { + return Optional.empty(); + } + } + + public void install( + Installer installer, + URL manifestUrl, + JavaVersion version + ) throws IOException, InterruptedException { + // Only download if the runtime is missing + // TODO: Support for marking runtime as corrupt? + if (this.getRuntime(version).isPresent()) return; + + File destination = new File(runtimesDir, version.getComponent()); + ObjectNode node = HttpRequest.get(manifestUrl) + .execute() + .expectResponseCode(200) + .returnContent() + .asJson(ObjectNode.class) + .without("gamecore"); + RuntimeList availableRuntimes = mapper.treeToValue(node, RuntimeList.class); + + RuntimePlatform platform = RuntimePlatform.from(Environment.getInstance()); + RuntimeInfo info = availableRuntimes.getRuntime(platform, version); + if (info == null) { + log.warning("Failed to match an available Java runtime - unsupported platform?"); + return; + } + + DownloadInfo downloadInfo = info.getManifest(); + RuntimeManifest manifest = HttpRequest + .get(url(downloadInfo.getUrl())) + .execute() + .expectResponseCode(200) + .returnContent() + .asJson(RuntimeManifest.class); + + Downloader downloader = installer.getDownloader(); + manifest.getFiles().forEach((filename, entry) -> { + File target = new File(destination, filename); + if (entry instanceof RuntimeManifestEntry.File) { + RuntimeManifestEntry.File file = (RuntimeManifestEntry.File) entry; + DownloadInfo fileDownload = file.getDownloads().get(Format.RAW); + + File tempFile = downloader.download(url(fileDownload.getUrl()), "", fileDownload.getSize(), filename); + installer.queue(new FileMover(tempFile, target)); + + if (fileDownload.getSha1() != null) { + installer.queue(new FileVerify(target, filename, fileDownload.getSha1())); + } + + if (file.isExecutable()) { + installer.queue(new FileSetExecutable(target)); + } + } else if (entry instanceof RuntimeManifestEntry.Directory) { + installer.queue(new CreateFolder(target)); + } else if (entry instanceof RuntimeManifestEntry.Link) { + String relpath = ((RuntimeManifestEntry.Link) entry).getTarget(); + Path existing = target.toPath().getParent().resolve(relpath); + + installer.queue(new CreateLink(target, existing)); + } + }); + + // Write runtime data + installer.queueLate(new InstallTask() { + @Override + public void execute(Launcher launcher) throws Exception { + RuntimeData rd = new RuntimeData(version.getComponent(), info.getVersion().getName(), platform.is64Bit()); + Persistence.write(new File(destination, "runtime.json"), rd); + } + + @Override + public double getProgress() { + return -1; + } + + @Override + public String getStatus() { + return tr("installer.adhoc.writingRuntimeData"); + } + }); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/update/runtime/RuntimeData.java b/launcher/src/main/java/com/skcraft/launcher/update/runtime/RuntimeData.java new file mode 100644 index 000000000..05471d821 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/update/runtime/RuntimeData.java @@ -0,0 +1,18 @@ +package com.skcraft.launcher.update.runtime; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +@Data +@Jacksonized +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +public class RuntimeData { + private final String component; + private final String version; + @JsonProperty("64Bit") + private final boolean is64Bit; +} diff --git a/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties b/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties index ef48f3c4b..d6f2379c2 100644 --- a/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties +++ b/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties @@ -168,6 +168,11 @@ installer.executing=Executing tasks... ({0} remaining) installer.copyingFile=Copying from {0} to {1} installer.movingFile=Moving {0} to {1} installer.runningProcessor=Running processor {0}: {1} +installer.creatingDirectory=Creating directory {0} +installer.verifyingFile=Verifying {0} +installer.settingExecutable=Setting permissions on {0} +installer.creatingLink=Linking {0} to {1} +installer.adhoc.writingRuntimeData=Writing Java runtime metadata updater.updating=Updating launcher... updater.updateRequiredButOffline=An update is required but you need to be in online mode. diff --git a/launcher/src/main/resources/com/skcraft/launcher/launcher.properties b/launcher/src/main/resources/com/skcraft/launcher/launcher.properties index 6100300cb..b28cd474a 100644 --- a/launcher/src/main/resources/com/skcraft/launcher/launcher.properties +++ b/launcher/src/main/resources/com/skcraft/launcher/launcher.properties @@ -14,6 +14,7 @@ offlinePlayerName=Player # Only change these if you know what you're doing. versionManifestUrl=https://launchermeta.mojang.com/mc/game/version_manifest.json +runtimeManifestUrl=https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json librariesSource=https://libraries.minecraft.net/ assetsSource=https://resources.download.minecraft.net/ microsoftClientId=d18bb4d8-a27f-4451-a87f-fe6de4436813