diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 3a820951..5a5245ed 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -3,6 +3,7 @@ * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2022 Jamie Mansfield + * Copyright (C) 2022 TheKodeToad * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -647,7 +648,17 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS { launchScript += "traits " + trait + "\n"; } - launchScript += "launcher onesix\n"; + + launchScript += "launcher "; + + // use legacy launcher if the traits are set + if (profile->getTraits().contains("legacyLaunch") || profile->getTraits().contains("alphaLaunch")) + launchScript += "legacy"; + else + launchScript += "standard"; + + launchScript += "\n"; + // qDebug() << "Generated launch script:" << launchScript; return launchScript; } diff --git a/libraries/README.md b/libraries/README.md index dc38477b..2971e32b 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -51,10 +51,10 @@ It: This means the process is essentially idle until the final command is sent. You can, for example, attach a profiler before you send it. -A `legacy` and `onesix` launchers are available. +A `legacy` and `standard` launchers are available. - `legacy` is intended for use with Minecraft versions < 1.6 and is deprecated. -- `onesix` can handle launching any Minecraft version, at the cost of some extra features `legacy` enables (custom window icon and title). +- `standard` can handle launching any Minecraft version, at the cost of some extra features `legacy` enables (custom window icon and title). Example (some parts have been censored): @@ -132,7 +132,7 @@ ext /home/peterix/minecraft/FTB/libraries/org/lwjgl/lwjgl/lwjgl-platform/2.9.1/l ext /home/peterix/minecraft/FTB/libraries/net/java/jinput/jinput-platform/2.0.5/jinput-platform-2.0.5-natives-linux.jar natives /home/peterix/minecraft/FTB/17ForgeTest/natives cp /home/peterix/minecraft/FTB/versions/1.7.10/1.7.10.jar -launcher onesix +launcher standard ``` Available under `GPL-3.0-only` (with classpath exception), sublicensed from its original `Apache-2.0` codebase diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt index df25414f..ee88d0f9 100644 --- a/libraries/launcher/CMakeLists.txt +++ b/libraries/launcher/CMakeLists.txt @@ -10,7 +10,9 @@ set(SRC org/prismlauncher/EntryPoint.java org/prismlauncher/Launcher.java org/prismlauncher/LauncherFactory.java - org/prismlauncher/impl/OneSixLauncher.java + org/prismlauncher/impl/AbstractLauncher.java + org/prismlauncher/impl/LegacyLauncher.java + org/prismlauncher/impl/StandardLauncher.java org/prismlauncher/applet/LegacyFrame.java org/prismlauncher/exception/ParameterNotFoundException.java org/prismlauncher/exception/ParseException.java diff --git a/libraries/launcher/org/prismlauncher/EntryPoint.java b/libraries/launcher/org/prismlauncher/EntryPoint.java index 9144e1f1..73ff9753 100644 --- a/libraries/launcher/org/prismlauncher/EntryPoint.java +++ b/libraries/launcher/org/prismlauncher/EntryPoint.java @@ -81,33 +81,35 @@ public final class EntryPoint { } private Action parseLine(String inData) throws ParseException { - String[] tokens = inData.split("\\s+", 2); - - if (tokens.length == 0) + if (inData.length() == 0) throw new ParseException("Unexpected empty string!"); - switch (tokens[0]) { - case "launch": { - return Action.Launch; - } + String first = inData; + String second = null; + int splitPoint = inData.indexOf(' '); - case "abort": { - return Action.Abort; - } + if (splitPoint != -1) { + first = first.substring(0, splitPoint); + second = inData.substring(splitPoint + 1); + } - default: { - if (tokens.length != 2) + switch (first) { + case "launch": + return Action.LAUNCH; + case "abort": + return Action.ABORT; + default: + if (second == null || second.isEmpty()) throw new ParseException("Error while parsing:" + inData); - params.add(tokens[0], tokens[1]); + params.add(first, second); - return Action.Proceed; - } + return Action.PROCEED; } } public int listen() { - Action action = Action.Proceed; + Action action = Action.PROCEED; try (BufferedReader reader = new BufferedReader(new InputStreamReader( System.in, @@ -115,21 +117,21 @@ public final class EntryPoint { ))) { String line; - while (action == Action.Proceed) { + while (action == Action.PROCEED) { if ((line = reader.readLine()) != null) { action = parseLine(line); } else { - action = Action.Abort; + action = Action.ABORT; } } } catch (IOException | ParseException e) { - LOGGER.log(Level.SEVERE, "Launcher ABORT due to exception:", e); + LOGGER.log(Level.SEVERE, "Launcher abort due to exception:", e); return 1; } // Main loop - if (action == Action.Abort) { + if (action == Action.ABORT) { LOGGER.info("Launch aborted by the launcher."); return 1; @@ -138,7 +140,7 @@ public final class EntryPoint { try { Launcher launcher = LauncherFactory - .getInstance() + .INSTANCE .createLauncher(params); launcher.launch(); @@ -148,7 +150,7 @@ public final class EntryPoint { LOGGER.log(Level.SEVERE, "Wrong argument.", e); return 1; - } catch (Exception e) { + } catch (Throwable e) { LOGGER.log(Level.SEVERE, "Exception caught from launcher.", e); return 1; @@ -156,9 +158,9 @@ public final class EntryPoint { } private enum Action { - Proceed, - Launch, - Abort + PROCEED, + LAUNCH, + ABORT } } diff --git a/libraries/launcher/org/prismlauncher/Launcher.java b/libraries/launcher/org/prismlauncher/Launcher.java index 7f25717b..50c2c9c8 100644 --- a/libraries/launcher/org/prismlauncher/Launcher.java +++ b/libraries/launcher/org/prismlauncher/Launcher.java @@ -18,6 +18,6 @@ package org.prismlauncher; public interface Launcher { - void launch() throws Exception; + void launch() throws Throwable; } diff --git a/libraries/launcher/org/prismlauncher/LauncherFactory.java b/libraries/launcher/org/prismlauncher/LauncherFactory.java index 98f2bbba..354ad1f0 100644 --- a/libraries/launcher/org/prismlauncher/LauncherFactory.java +++ b/libraries/launcher/org/prismlauncher/LauncherFactory.java @@ -35,7 +35,8 @@ package org.prismlauncher; -import org.prismlauncher.impl.OneSixLauncher; +import org.prismlauncher.impl.LegacyLauncher; +import org.prismlauncher.impl.StandardLauncher; import org.prismlauncher.utils.Parameters; import java.util.HashMap; @@ -43,15 +44,21 @@ import java.util.Map; public final class LauncherFactory { - private static final LauncherFactory INSTANCE = new LauncherFactory(); + public static final LauncherFactory INSTANCE = new LauncherFactory(); private final Map launcherRegistry = new HashMap<>(); private LauncherFactory() { - launcherRegistry.put("onesix", new LauncherProvider() { + launcherRegistry.put("standard", new LauncherProvider() { @Override public Launcher provide(Parameters parameters) { - return new OneSixLauncher(parameters); + return new StandardLauncher(parameters); + } + }); + launcherRegistry.put("legacy", new LauncherProvider() { + @Override + public Launcher provide(Parameters parameters) { + return new LegacyLauncher(parameters); } }); } @@ -67,10 +74,6 @@ public final class LauncherFactory { return launcherProvider.provide(parameters); } - public static LauncherFactory getInstance() { - return INSTANCE; - } - public interface LauncherProvider { Launcher provide(Parameters parameters); diff --git a/libraries/launcher/org/prismlauncher/applet/LegacyFrame.java b/libraries/launcher/org/prismlauncher/applet/LegacyFrame.java index 4413efa8..f3359fca 100644 --- a/libraries/launcher/org/prismlauncher/applet/LegacyFrame.java +++ b/libraries/launcher/org/prismlauncher/applet/LegacyFrame.java @@ -34,6 +34,7 @@ import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; +@SuppressWarnings("removal") public final class LegacyFrame extends Frame { private static final Logger LOGGER = Logger.getLogger("LegacyFrame"); @@ -105,7 +106,7 @@ public final class LegacyFrame extends Frame { appletWrap.setParameter("username", user); appletWrap.setParameter("sessionid", session); - appletWrap.setParameter("stand-alone", "true"); // Show the quit button. + appletWrap.setParameter("stand-alone", "true"); // Show the quit button. TODO: why won't this work? appletWrap.setParameter("haspaid", "true"); // Some old versions need this for world saves to work. appletWrap.setParameter("demo", isDemo ? "true" : "false"); appletWrap.setParameter("fullscreen", "false"); diff --git a/libraries/launcher/org/prismlauncher/impl/AbstractLauncher.java b/libraries/launcher/org/prismlauncher/impl/AbstractLauncher.java new file mode 100644 index 00000000..49a984f5 --- /dev/null +++ b/libraries/launcher/org/prismlauncher/impl/AbstractLauncher.java @@ -0,0 +1,95 @@ +/* Copyright 2012-2021 MultiMC Contributors + * + * 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.prismlauncher.impl; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.prismlauncher.Launcher; +import org.prismlauncher.exception.ParseException; +import org.prismlauncher.utils.Parameters; + +public abstract class AbstractLauncher implements Launcher { + + private static final int DEFAULT_WINDOW_WIDTH = 854; + private static final int DEFAULT_WINDOW_HEIGHT = 480; + + // parameters, separated from ParamBucket + protected final List mcParams; + private final String mainClass; + + // secondary parameters + protected final int width; + protected final int height; + protected final boolean maximize; + + protected final String serverAddress, serverPort; + + protected final ClassLoader classLoader; + + public AbstractLauncher(Parameters params) { + classLoader = ClassLoader.getSystemClassLoader(); + + mcParams = params.allSafe("param", new ArrayList()); + mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); + + serverAddress = params.firstSafe("serverAddress", null); + serverPort = params.firstSafe("serverPort", null); + + String windowParams = params.firstSafe("windowParams", null); + + if ("max".equals(windowParams) || windowParams == null) { + maximize = windowParams != null; + + width = DEFAULT_WINDOW_WIDTH; + height = DEFAULT_WINDOW_HEIGHT; + } else { + maximize = false; + + int byIndex = windowParams.indexOf('x'); + + if (byIndex != -1) { + try { + width = Integer.parseInt(windowParams.substring(0, byIndex)); + height = Integer.parseInt(windowParams.substring(byIndex + 1)); + return; + } catch(NumberFormatException pass) { + } + } + + throw new ParseException("Invalid window size parameter value: " + windowParams); + } + } + + protected Class loadMain() throws ClassNotFoundException { + return classLoader.loadClass(mainClass); + } + + protected void loadAndInvokeMain() throws Throwable, ClassNotFoundException { + invokeMain(loadMain()); + } + + protected void invokeMain(Class mainClass) throws Throwable { + MethodHandle method = MethodHandles.lookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class)); + + method.invokeExact(mcParams.toArray(new String[0])); + } + +} diff --git a/libraries/launcher/org/prismlauncher/impl/LegacyLauncher.java b/libraries/launcher/org/prismlauncher/impl/LegacyLauncher.java new file mode 100644 index 00000000..30a4dba7 --- /dev/null +++ b/libraries/launcher/org/prismlauncher/impl/LegacyLauncher.java @@ -0,0 +1,104 @@ +/* Copyright 2012-2021 MultiMC Contributors + * + * 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.prismlauncher.impl; + +import java.applet.Applet; +import java.io.File; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.prismlauncher.applet.LegacyFrame; +import org.prismlauncher.utils.Parameters; +import org.prismlauncher.utils.Utils; + +@SuppressWarnings("removal") +public final class LegacyLauncher extends AbstractLauncher { + + private static final Logger LOGGER = Logger.getLogger("LegacyLauncher"); + + private final String user, session; + private final String title; + private final String appletClass; + + private final boolean noApplet; + private final String cwd; + + public LegacyLauncher(Parameters params) { + super(params); + + user = params.first("userName"); + session = params.first("sessionId"); + title = params.firstSafe("windowTitle", "Minecraft"); + appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); + + List traits = params.allSafe("traits", Collections.emptyList()); + noApplet = traits.contains("noapplet"); + + cwd = System.getProperty("user.dir"); + } + + @Override + public void launch() throws Throwable { + Class main = loadMain(); + Field gameDirField = Utils.getMinecraftGameDirField(main); + + if (gameDirField == null) { + LOGGER.warning("Could not find Mineraft path field."); + } else { + gameDirField.setAccessible(true); + gameDirField.set(null, new File(cwd)); + } + + if (!noApplet) { + LOGGER.info("Launching with applet wrapper..."); + + try { + Class appletClass = classLoader.loadClass(this.appletClass); + + MethodHandle constructor = MethodHandles.lookup().findConstructor(appletClass, MethodType.methodType(void.class)); + Applet applet = (Applet) constructor.invoke(); + + LegacyFrame window = new LegacyFrame(title, applet); + + window.start( + user, + session, + width, + height, + maximize, + serverAddress, + serverPort, + mcParams.contains("--demo") + ); + + return; + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, "Applet wrapper failed:", e); + + LOGGER.warning("Falling back to using main class."); + } + } + + invokeMain(main); + } + +} diff --git a/libraries/launcher/org/prismlauncher/impl/OneSixLauncher.java b/libraries/launcher/org/prismlauncher/impl/OneSixLauncher.java deleted file mode 100644 index d6443826..00000000 --- a/libraries/launcher/org/prismlauncher/impl/OneSixLauncher.java +++ /dev/null @@ -1,190 +0,0 @@ -/* Copyright 2012-2021 MultiMC Contributors - * - * 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.prismlauncher.impl; - -import org.prismlauncher.Launcher; -import org.prismlauncher.applet.LegacyFrame; -import org.prismlauncher.utils.Parameters; -import org.prismlauncher.utils.Utils; - -import java.applet.Applet; -import java.io.File; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.List; -import java.util.ArrayList; -import java.util.logging.Level; -import java.util.logging.Logger; - -public final class OneSixLauncher implements Launcher { - - private static final int DEFAULT_WINDOW_WIDTH = 854; - private static final int DEFAULT_WINDOW_HEIGHT = 480; - - private static final Logger LOGGER = Logger.getLogger("OneSixLauncher"); - - // parameters, separated from ParamBucket - private final List mcParams; - private final List traits; - private final String appletClass; - private final String mainClass; - private final String userName, sessionId; - private final String windowTitle; - - // secondary parameters - private final int winSizeW; - private final int winSizeH; - private final boolean maximize; - private final String cwd; - - private final String serverAddress; - private final String serverPort; - - private final ClassLoader classLoader; - - public OneSixLauncher(Parameters params) { - classLoader = ClassLoader.getSystemClassLoader(); - - mcParams = params.allSafe("param", new ArrayList()); - mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); - appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); - traits = params.allSafe("traits", new ArrayList()); - - userName = params.first("userName"); - sessionId = params.first("sessionId"); - windowTitle = params.firstSafe("windowTitle", "Minecraft"); - - serverAddress = params.firstSafe("serverAddress", null); - serverPort = params.firstSafe("serverPort", null); - - cwd = System.getProperty("user.dir"); - - String windowParams = params.firstSafe("windowParams", null); - - if (windowParams != null) { - String[] dimStrings = windowParams.split("x"); - - if (windowParams.equalsIgnoreCase("max")) { - maximize = true; - - winSizeW = DEFAULT_WINDOW_WIDTH; - winSizeH = DEFAULT_WINDOW_HEIGHT; - } else if (dimStrings.length == 2) { - maximize = false; - - winSizeW = Integer.parseInt(dimStrings[0]); - winSizeH = Integer.parseInt(dimStrings[1]); - } else { - throw new IllegalArgumentException("Unexpected window size parameter value: " + windowParams); - } - } else { - maximize = false; - - winSizeW = DEFAULT_WINDOW_WIDTH; - winSizeH = DEFAULT_WINDOW_HEIGHT; - } - } - - private void invokeMain(Class mainClass) throws Exception { - Method method = mainClass.getMethod("main", String[].class); - - method.invoke(null, (Object) mcParams.toArray(new String[0])); - } - - private void legacyLaunch() throws Exception { - // Get the Minecraft Class and set the base folder - Class minecraftClass = classLoader.loadClass(mainClass); - - Field baseDirField = Utils.getMinecraftBaseDirField(minecraftClass); - - if (baseDirField == null) { - LOGGER.warning("Could not find Minecraft path field."); - } else { - baseDirField.setAccessible(true); - - baseDirField.set(null, new File(cwd)); - } - - System.setProperty("minecraft.applet.TargetDirectory", cwd); - - if (!traits.contains("noapplet")) { - LOGGER.info("Launching with applet wrapper..."); - - try { - Class mcAppletClass = classLoader.loadClass(appletClass); - - Applet mcApplet = (Applet) mcAppletClass.getConstructor().newInstance(); - - LegacyFrame mcWindow = new LegacyFrame(windowTitle, mcApplet); - - mcWindow.start( - userName, - sessionId, - winSizeW, - winSizeH, - maximize, - serverAddress, - serverPort, - mcParams.contains("--demo") - ); - - return; - } catch (Exception e) { - LOGGER.log(Level.SEVERE, "Applet wrapper failed: ", e); - - LOGGER.warning("Falling back to using main class."); - } - } - - invokeMain(minecraftClass); - } - - private void launchWithMainClass() throws Exception { - // window size, title and state, onesix - - // FIXME: there is no good way to maximize the minecraft window in onesix. - // the following often breaks linux screen setups - // mcparams.add("--fullscreen"); - - if (!maximize) { - mcParams.add("--width"); - mcParams.add(Integer.toString(winSizeW)); - mcParams.add("--height"); - mcParams.add(Integer.toString(winSizeH)); - } - - if (serverAddress != null) { - mcParams.add("--server"); - mcParams.add(serverAddress); - mcParams.add("--port"); - mcParams.add(serverPort); - } - - invokeMain(classLoader.loadClass(mainClass)); - } - - @Override - public void launch() throws Exception { - if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch")) { - // legacy launch uses the applet wrapper - legacyLaunch(); - } else { - // normal launch just calls main() - launchWithMainClass(); - } - } - -} diff --git a/libraries/launcher/org/prismlauncher/impl/StandardLauncher.java b/libraries/launcher/org/prismlauncher/impl/StandardLauncher.java new file mode 100644 index 00000000..c651b060 --- /dev/null +++ b/libraries/launcher/org/prismlauncher/impl/StandardLauncher.java @@ -0,0 +1,51 @@ +/* Copyright 2012-2021 MultiMC Contributors + * + * 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.prismlauncher.impl; + +import org.prismlauncher.utils.Parameters; + +public final class StandardLauncher extends AbstractLauncher { + + public StandardLauncher(Parameters params) { + super(params); + } + + @Override + public void launch() throws Throwable { + // window size, title and state + + // FIXME: there is no good way to maximize the minecraft window from here. + // the following often breaks linux screen setups + // mcparams.add("--fullscreen"); + + if (!maximize) { + mcParams.add("--width"); + mcParams.add(Integer.toString(width)); + mcParams.add("--height"); + mcParams.add(Integer.toString(height)); + } + + if (serverAddress != null) { + mcParams.add("--server"); + mcParams.add(serverAddress); + mcParams.add("--port"); + mcParams.add(serverPort); + } + + loadAndInvokeMain(); + } + +} diff --git a/libraries/launcher/org/prismlauncher/utils/Parameters.java b/libraries/launcher/org/prismlauncher/utils/Parameters.java index 98a40c28..dcaba18d 100644 --- a/libraries/launcher/org/prismlauncher/utils/Parameters.java +++ b/libraries/launcher/org/prismlauncher/utils/Parameters.java @@ -25,22 +25,22 @@ import java.util.Map; public final class Parameters { - private final Map> paramsMap = new HashMap<>(); + private final Map> map = new HashMap<>(); public void add(String key, String value) { - List params = paramsMap.get(key); + List params = map.get(key); if (params == null) { params = new ArrayList<>(); - paramsMap.put(key, params); + map.put(key, params); } params.add(value); } public List all(String key) throws ParameterNotFoundException { - List params = paramsMap.get(key); + List params = map.get(key); if (params == null) throw new ParameterNotFoundException(key); @@ -49,7 +49,7 @@ public final class Parameters { } public List allSafe(String key, List def) { - List params = paramsMap.get(key); + List params = map.get(key); if (params == null || params.isEmpty()) return def; @@ -67,7 +67,7 @@ public final class Parameters { } public String firstSafe(String key, String def) { - List params = paramsMap.get(key); + List params = map.get(key); if (params == null || params.isEmpty()) return def; diff --git a/libraries/launcher/org/prismlauncher/utils/Utils.java b/libraries/launcher/org/prismlauncher/utils/Utils.java index ae9a4de2..79f5367b 100644 --- a/libraries/launcher/org/prismlauncher/utils/Utils.java +++ b/libraries/launcher/org/prismlauncher/utils/Utils.java @@ -29,7 +29,7 @@ public final class Utils { * * @param clazz the class to scan */ - public static Field getMinecraftBaseDirField(Class clazz) { + public static Field getMinecraftGameDirField(Class clazz) { for (Field f : clazz.getDeclaredFields()) { // Has to be File if (f.getType() != File.class)