From 6a409b6b2373a01dc72253794d6a77dbceaaa32e Mon Sep 17 00:00:00 2001 From: sychic <47618543+Sychic@users.noreply.github.com> Date: Tue, 18 Jun 2024 14:22:26 -0700 Subject: [PATCH] stage2/ml9: create mixin bootstrap --- .../loader/stage2/DedicatedJarLoader.java | 65 +++++++++++++++ .../loader/stage2/EssentialLoader.java | 19 +++++ .../gg/essential/loader/stage2/RestartUI.java | 81 +++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/DedicatedJarLoader.java create mode 100644 stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/RestartUI.java diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/DedicatedJarLoader.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/DedicatedJarLoader.java new file mode 100644 index 0000000..38b3c5a --- /dev/null +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/DedicatedJarLoader.java @@ -0,0 +1,65 @@ +package gg.essential.loader.stage2; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Path; + +public class DedicatedJarLoader { + private static final String BASE_URL = System.getProperty( + "essential.download.url", + System.getenv().getOrDefault("ESSENTIAL_DOWNLOAD_URL", "https://api.essential.gg/mods") + ); + private static final String VERSION_URL = BASE_URL + "/v1/essential:essential-pinned/versions/stable/platforms/%s"; + private static final String DOWNLOAD_URL = VERSION_URL + "/download"; + + protected static void downloadDedicatedJar(LoaderUI ui, Path modsDir, String gameVersion) throws IOException { + final JsonObject meta = getEssentialDownloadMeta(gameVersion); + final URL url = new URL(meta.get("url").getAsString()); + final URLConnection connection = url.openConnection(); + + ui.setDownloadSize(connection.getContentLength()); + + final String essentialVersion = getEssentialVersionMeta(gameVersion).get("version").getAsString(); + final Path target = modsDir.resolve(String.format("Essential %s (%s).jar", gameVersion, essentialVersion)); + + try ( + final InputStream in = connection.getInputStream(); + final OutputStream out = Files.newOutputStream(target); + ) { + + final byte[] buffer = new byte[1024]; + int totalRead = 0; + int read; + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + totalRead += read; + ui.setDownloaded(totalRead); + } + } + } + + private static JsonObject getEssentialMeta(URL url) throws IOException { + String response; + try (final InputStream in = url.openStream()) { + response = new String(in.readAllBytes()); + } + JsonElement json = new JsonParser().parse(response); + return json.getAsJsonObject(); + } + + private static JsonObject getEssentialVersionMeta(String gameVersion) throws IOException { + return getEssentialMeta(new URL(String.format(VERSION_URL, gameVersion))); + } + + private static JsonObject getEssentialDownloadMeta(String gameVersion) throws IOException { + return getEssentialMeta(new URL(String.format(DOWNLOAD_URL, gameVersion))); + } +} diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialLoader.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialLoader.java index 4be0bb8..079a8e5 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialLoader.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialLoader.java @@ -1,7 +1,10 @@ package gg.essential.loader.stage2; import cpw.mods.modlauncher.api.ITransformationService; +import gg.essential.loader.stage2.jvm.ForkedJvmLoaderSwingUI; +import net.minecraftforge.fml.loading.FMLLoader; +import java.io.IOException; import java.nio.file.Path; /** @@ -26,4 +29,20 @@ public ITransformationService getTransformationService() { public void load() { // delayed until ModLauncher exposes the MC version } + + @SuppressWarnings("unused") // called via reflection from stage1 + public void loadFromMixin(Path gameDir) throws IOException { + final Path modsDir = gameDir.resolve("mods"); + LoaderUI ui = LoaderUI.all( + new LoaderLoggingUI().updatesEveryMillis(1000), + new ForkedJvmLoaderSwingUI().updatesEveryMillis(1000 / 60) + ); + ui.start(); + DedicatedJarLoader.downloadDedicatedJar(ui, modsDir, "forge_" + FMLLoader.versionInfo().mcVersion()); + ui.complete(); + RestartUI restartUI = new RestartUI("Restart Required!", "One of the mods you have installed requires Essential. To complete the installation process, please restart."); + restartUI.show(); + restartUI.waitForClose(); + System.exit(0); + } } diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/RestartUI.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/RestartUI.java new file mode 100644 index 0000000..1cb9102 --- /dev/null +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/RestartUI.java @@ -0,0 +1,81 @@ +package gg.essential.loader.stage2; + +import gg.essential.loader.stage2.components.EssentialStyle; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import static gg.essential.loader.stage2.components.ButtonShadowBorder.X_SHADOW; +import static gg.essential.loader.stage2.components.ButtonShadowBorder.Y_SHADOW; + +public class RestartUI implements EssentialStyle { + private final CompletableFuture closedFuture = new CompletableFuture<>(); + + private final JFrame frame = makeFrame(it -> closedFuture.complete(null)); + + private final String title; + private final String description; + + public RestartUI(String title, String description) { + this.title = title; + this.description = description; + } + + public void show() { + final List htmlLabels = new ArrayList<>(); + + final JPanel content = makeContent(frame); + content.setBorder(new EmptyBorder(0, 60 - X_SHADOW, 0, 60 - X_SHADOW)); + content.add(Box.createHorizontalStrut(CONTENT_WIDTH)); + + htmlLabels.add(makeTitle(content, html(centered(title)))); + + final JLabel explanation = new JLabel(html(centered(description)), SwingConstants.CENTER); + explanation.setMaximumSize(new Dimension(CONTENT_WIDTH, Integer.MAX_VALUE)); + explanation.setForeground(COLOR_FOREGROUND); + explanation.setAlignmentX(Container.CENTER_ALIGNMENT); + if (Fonts.medium != null) { + explanation.setFont(Fonts.medium.deriveFont(16F)); + } + content.add(explanation); + htmlLabels.add(explanation); + + content.add(Box.createVerticalStrut(32 - Y_SHADOW)); + + final JPanel buttons = new JPanel(); + buttons.setOpaque(false); + buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS)); + buttons.add(makeButton("Restart", COLOR_PRIMARY_BUTTON, COLOR_BUTTON_HOVER, () -> closedFuture.complete(true))); + content.add(buttons); + + content.add(Box.createVerticalStrut(32 - Y_SHADOW)); + + frame.pack(); + + htmlLabels.forEach(this::fixJLabelHeight); + frame.pack(); + + frame.setLocationRelativeTo(null); + frame.setVisible(true); + } + + public Boolean waitForClose() { + Boolean verdict = closedFuture.join(); + close(); + return verdict; + } + + public void close() { + frame.dispose(); + } + + public static void main(String[] args) { + RestartUI ui = new RestartUI("Restart required!", "A restart is required for something."); + ui.show(); + System.out.println(ui.waitForClose()); + } +}