diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt index 0eccae8b..e0149482 100644 --- a/libraries/launcher/CMakeLists.txt +++ b/libraries/launcher/CMakeLists.txt @@ -9,12 +9,13 @@ set(CMAKE_JAVA_COMPILE_FLAGS -target 8 -source 8 -Xlint:deprecation -Xlint:unche set(SRC org/multimc/EntryPoint.java org/multimc/Launcher.java - org/multimc/LegacyFrame.java - org/multimc/NotFoundException.java - org/multimc/ParamBucket.java - org/multimc/ParseException.java - org/multimc/Utils.java - org/multimc/onesix/OneSixLauncher.java + org/multimc/LauncherFactory.java + org/multimc/impl/OneSixLauncher.java + org/multimc/applet/LegacyFrame.java + org/multimc/exception/ParameterNotFoundException.java + org/multimc/exception/ParseException.java + org/multimc/utils/ParamBucket.java + org/multimc/utils/Utils.java net/minecraft/Launcher.java ) add_jar(NewLaunch ${SRC}) diff --git a/libraries/launcher/net/minecraft/Launcher.java b/libraries/launcher/net/minecraft/Launcher.java index b6b0a574..04201047 100644 --- a/libraries/launcher/net/minecraft/Launcher.java +++ b/libraries/launcher/net/minecraft/Launcher.java @@ -16,29 +16,28 @@ package net.minecraft; -import java.util.TreeMap; -import java.util.Map; -import java.net.URL; -import java.awt.Dimension; -import java.awt.BorderLayout; -import java.awt.Graphics; import java.applet.Applet; import java.applet.AppletStub; +import java.awt.*; import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.TreeMap; + +public class Launcher extends Applet implements AppletStub { + + private final Map params = new TreeMap<>(); + + private boolean active = false; -public class Launcher extends Applet implements AppletStub -{ private Applet wrappedApplet; private URL documentBase; - private boolean active = false; - private final Map params; - - public Launcher(Applet applet, URL documentBase) - { - params = new TreeMap(); + public Launcher(Applet applet, URL documentBase) { this.setLayout(new BorderLayout()); + this.add(applet, "Center"); + this.wrappedApplet = applet; this.documentBase = documentBase; } @@ -48,8 +47,7 @@ public class Launcher extends Applet implements AppletStub params.put(name, value); } - public void replace(Applet applet) - { + public void replace(Applet applet) { this.wrappedApplet = applet; applet.setStub(this); @@ -65,67 +63,60 @@ public class Launcher extends Applet implements AppletStub } @Override - public String getParameter(String name) - { + public String getParameter(String name) { String param = params.get(name); + if (param != null) return param; - try - { + + try { return super.getParameter(name); - } catch (Exception ignore){} + } catch (Exception ignore) {} + return null; } @Override - public boolean isActive() - { + public boolean isActive() { return active; } @Override - public void appletResize(int width, int height) - { + public void appletResize(int width, int height) { wrappedApplet.resize(width, height); } @Override - public void resize(int width, int height) - { + public void resize(int width, int height) { wrappedApplet.resize(width, height); } @Override - public void resize(Dimension d) - { + public void resize(Dimension d) { wrappedApplet.resize(d); } @Override - public void init() - { + public void init() { if (wrappedApplet != null) - { wrappedApplet.init(); - } } @Override - public void start() - { + public void start() { wrappedApplet.start(); + active = true; } @Override - public void stop() - { + public void stop() { wrappedApplet.stop(); + active = false; } - public void destroy() - { + public void destroy() { wrappedApplet.destroy(); } @@ -136,34 +127,35 @@ public class Launcher extends Applet implements AppletStub } catch (MalformedURLException e) { e.printStackTrace(); } + return null; } @Override - public URL getDocumentBase() - { + public URL getDocumentBase() { try { // Special case only for Classic versions if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang")) { return new URL("http", "www.minecraft.net", 80, "/game/", null); } + return new URL("http://www.minecraft.net/game/"); } catch (MalformedURLException e) { e.printStackTrace(); } + return null; } @Override - public void setVisible(boolean b) - { + public void setVisible(boolean b) { super.setVisible(b); + wrappedApplet.setVisible(b); } - public void update(Graphics paramGraphics) - { - } - public void paint(Graphics paramGraphics) - { - } -} \ No newline at end of file + + public void update(Graphics paramGraphics) {} + + public void paint(Graphics paramGraphics) {} + +} diff --git a/libraries/launcher/org/multimc/EntryPoint.java b/libraries/launcher/org/multimc/EntryPoint.java index b626d095..be06d1b4 100644 --- a/libraries/launcher/org/multimc/EntryPoint.java +++ b/libraries/launcher/org/multimc/EntryPoint.java @@ -14,7 +14,8 @@ package org.multimc;/* * limitations under the License. */ -import org.multimc.onesix.OneSixLauncher; +import org.multimc.exception.ParseException; +import org.multimc.utils.ParamBucket; import java.io.BufferedReader; import java.io.IOException; @@ -23,31 +24,27 @@ import java.nio.charset.StandardCharsets; import java.util.logging.Level; import java.util.logging.Logger; -public class EntryPoint -{ +public final class EntryPoint { private static final Logger LOGGER = Logger.getLogger("EntryPoint"); private final ParamBucket params = new ParamBucket(); - private org.multimc.Launcher launcher; + private String launcherType; - public static void main(String[] args) - { + public static void main(String[] args) { EntryPoint listener = new EntryPoint(); int retCode = listener.listen(); - if (retCode != 0) - { + if (retCode != 0) { LOGGER.info("Exiting with " + retCode); System.exit(retCode); } } - private Action parseLine(String inData) throws ParseException - { + private Action parseLine(String inData) throws ParseException { String[] tokens = inData.split("\\s+", 2); if (tokens.length == 0) @@ -66,15 +63,9 @@ public class EntryPoint if (tokens.length != 2) throw new ParseException("Expected 2 tokens, got " + tokens.length); - if (tokens[1].equals("onesix")) { - launcher = new OneSixLauncher(); + launcherType = tokens[1]; - LOGGER.info("Using onesix launcher."); - - return Action.Proceed; - } else { - throw new ParseException("Invalid launcher type: " + tokens[1]); - } + return Action.Proceed; } default: { @@ -88,8 +79,7 @@ public class EntryPoint } } - public int listen() - { + public int listen() { Action action = Action.Proceed; try (BufferedReader reader = new BufferedReader(new InputStreamReader( @@ -112,16 +102,31 @@ public class EntryPoint } // Main loop - if (action == Action.Abort) - { + if (action == Action.Abort) { LOGGER.info("Launch aborted by the launcher."); return 1; } - if (launcher != null) - { - return launcher.launch(params); + if (launcherType != null) { + try { + Launcher launcher = + LauncherFactory + .getInstance() + .createLauncher(launcherType, params); + + launcher.launch(); + + return 0; + } catch (IllegalArgumentException e) { + LOGGER.log(Level.SEVERE, "Wrong argument.", e); + + return 1; + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Exception caught from launcher.", e); + + return 1; + } } LOGGER.log(Level.SEVERE, "No valid launcher implementation specified."); diff --git a/libraries/launcher/org/multimc/Launcher.java b/libraries/launcher/org/multimc/Launcher.java index c5e8fbc1..bc0b525e 100644 --- a/libraries/launcher/org/multimc/Launcher.java +++ b/libraries/launcher/org/multimc/Launcher.java @@ -16,7 +16,8 @@ package org.multimc; -public interface Launcher -{ - int launch(ParamBucket params); +public interface Launcher { + + void launch() throws Exception; + } diff --git a/libraries/launcher/org/multimc/LauncherFactory.java b/libraries/launcher/org/multimc/LauncherFactory.java new file mode 100644 index 00000000..b5d0dd5b --- /dev/null +++ b/libraries/launcher/org/multimc/LauncherFactory.java @@ -0,0 +1,34 @@ +package org.multimc; + +import org.multimc.impl.OneSixLauncher; +import org.multimc.utils.ParamBucket; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +public final class LauncherFactory { + + private static final LauncherFactory INSTANCE = new LauncherFactory(); + + private final Map> launcherRegistry = new HashMap<>(); + + private LauncherFactory() { + launcherRegistry.put("onesix", OneSixLauncher::new); + } + + public Launcher createLauncher(String name, ParamBucket parameters) { + Function launcherCreator = + launcherRegistry.get(name); + + if (launcherCreator == null) + throw new IllegalArgumentException("Invalid launcher type: " + name); + + return launcherCreator.apply(parameters); + } + + public static LauncherFactory getInstance() { + return INSTANCE; + } + +} diff --git a/libraries/launcher/org/multimc/LegacyFrame.java b/libraries/launcher/org/multimc/LegacyFrame.java deleted file mode 100644 index 985a10e6..00000000 --- a/libraries/launcher/org/multimc/LegacyFrame.java +++ /dev/null @@ -1,176 +0,0 @@ -package org.multimc;/* - * 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. - */ - -import net.minecraft.Launcher; - -import javax.imageio.ImageIO; -import java.applet.Applet; -import java.awt.*; -import java.awt.event.WindowEvent; -import java.awt.event.WindowListener; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Scanner; - -public class LegacyFrame extends Frame implements WindowListener -{ - private Launcher appletWrap = null; - public LegacyFrame(String title) - { - super ( title ); - BufferedImage image; - try { - image = ImageIO.read ( new File ( "icon.png" ) ); - setIconImage ( image ); - } catch ( IOException e ) { - e.printStackTrace(); - } - this.addWindowListener ( this ); - } - - public void start ( - Applet mcApplet, - String user, - String session, - int winSizeW, - int winSizeH, - boolean maximize, - String serverAddress, - String serverPort - ) - { - try { - appletWrap = new Launcher( mcApplet, new URL ( "http://www.minecraft.net/game" ) ); - } catch ( MalformedURLException ignored ) {} - - // Implements support for launching in to multiplayer on classic servers using a mpticket - // file generated by an external program and stored in the instance's root folder. - File mpticketFile = null; - Scanner fileReader = null; - try { - mpticketFile = new File(System.getProperty("user.dir") + "/../mpticket").getCanonicalFile(); - fileReader = new Scanner(new FileInputStream(mpticketFile), "ascii"); - String[] mpticketParams = new String[3]; - - for(int i=0;i<3;i++) { - if(fileReader.hasNextLine()) { - mpticketParams[i] = fileReader.nextLine(); - } else { - throw new IllegalArgumentException(); - } - } - - // Assumes parameters are valid and in the correct order - appletWrap.setParameter("server", mpticketParams[0]); - appletWrap.setParameter("port", mpticketParams[1]); - appletWrap.setParameter("mppass", mpticketParams[2]); - - fileReader.close(); - mpticketFile.delete(); - } - catch (FileNotFoundException e) {} - catch (IllegalArgumentException e) { - - fileReader.close(); - File mpticketFileCorrupt = new File(System.getProperty("user.dir") + "/../mpticket.corrupt"); - if(mpticketFileCorrupt.exists()) { - mpticketFileCorrupt.delete(); - } - mpticketFile.renameTo(mpticketFileCorrupt); - - System.err.println("Malformed mpticket file, missing argument."); - e.printStackTrace(System.err); - System.exit(-1); - } - catch (Exception e) { - e.printStackTrace(System.err); - System.exit(-1); - } - - if (serverAddress != null) - { - appletWrap.setParameter("server", serverAddress); - appletWrap.setParameter("port", serverPort); - } - - appletWrap.setParameter ( "username", user ); - appletWrap.setParameter ( "sessionid", session ); - appletWrap.setParameter ( "stand-alone", "true" ); // Show the quit button. - appletWrap.setParameter ( "haspaid", "true" ); // Some old versions need this for world saves to work. - appletWrap.setParameter ( "demo", "false" ); - appletWrap.setParameter ( "fullscreen", "false" ); - mcApplet.setStub(appletWrap); - this.add ( appletWrap ); - appletWrap.setPreferredSize ( new Dimension (winSizeW, winSizeH) ); - this.pack(); - this.setLocationRelativeTo ( null ); - this.setResizable ( true ); - if ( maximize ) { - this.setExtendedState ( MAXIMIZED_BOTH ); - } - validate(); - appletWrap.init(); - appletWrap.start(); - setVisible ( true ); - } - - @Override - public void windowActivated ( WindowEvent e ) {} - - @Override - public void windowClosed ( WindowEvent e ) {} - - @Override - public void windowClosing ( WindowEvent e ) - { - new Thread() { - public void run() { - try { - Thread.sleep ( 30000L ); - } catch ( InterruptedException localInterruptedException ) { - localInterruptedException.printStackTrace(); - } - System.out.println ( "FORCING EXIT!" ); - System.exit ( 0 ); - } - } - .start(); - - if ( appletWrap != null ) { - appletWrap.stop(); - appletWrap.destroy(); - } - // old minecraft versions can hang without this >_< - System.exit ( 0 ); - } - - @Override - public void windowDeactivated ( WindowEvent e ) {} - - @Override - public void windowDeiconified ( WindowEvent e ) {} - - @Override - public void windowIconified ( WindowEvent e ) {} - - @Override - public void windowOpened ( WindowEvent e ) {} -} diff --git a/libraries/launcher/org/multimc/Utils.java b/libraries/launcher/org/multimc/Utils.java deleted file mode 100644 index e48029c2..00000000 --- a/libraries/launcher/org/multimc/Utils.java +++ /dev/null @@ -1,86 +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.multimc; - -import java.io.File; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.List; - -public class Utils -{ - /** - * Combine two parts of a path. - * - * @param path1 - * @param path2 - * @return the paths, combined - */ - public static String combine(String path1, String path2) - { - File file1 = new File(path1); - File file2 = new File(file1, path2); - return file2.getPath(); - } - - /** - * Join a list of strings into a string using a separator! - * - * @param strings the string list to join - * @param separator the glue - * @return the result. - */ - public static String join(List strings, String separator) - { - StringBuilder sb = new StringBuilder(); - String sep = ""; - for (String s : strings) - { - sb.append(sep).append(s); - sep = separator; - } - return sb.toString(); - } - - /** - * Finds a field that looks like a Minecraft base folder in a supplied class - * - * @param mc the class to scan - */ - public static Field getMCPathField(Class mc) - { - Field[] fields = mc.getDeclaredFields(); - - for (Field f : fields) - { - if (f.getType() != File.class) - { - // Has to be File - continue; - } - if (f.getModifiers() != (Modifier.PRIVATE + Modifier.STATIC)) - { - // And Private Static. - continue; - } - return f; - } - return null; - } - -} - diff --git a/libraries/launcher/org/multimc/applet/LegacyFrame.java b/libraries/launcher/org/multimc/applet/LegacyFrame.java new file mode 100644 index 00000000..a5e6c170 --- /dev/null +++ b/libraries/launcher/org/multimc/applet/LegacyFrame.java @@ -0,0 +1,167 @@ +package org.multimc.applet;/* + * 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. + */ + +import net.minecraft.Launcher; + +import javax.imageio.ImageIO; +import java.applet.Applet; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; + +public final class LegacyFrame extends Frame { + + private static final Logger LOGGER = Logger.getLogger("LegacyFrame"); + + private Launcher appletWrap; + + public LegacyFrame(String title) { + super(title); + + try { + setIconImage(ImageIO.read(new File("icon.png"))); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Unable to read Minecraft icon!", e); + } + + this.addWindowListener(new ForceExitHandler()); + } + + public void start ( + Applet mcApplet, + String user, + String session, + int winSizeW, + int winSizeH, + boolean maximize, + String serverAddress, + String serverPort + ) { + try { + appletWrap = new Launcher(mcApplet, new URL("http://www.minecraft.net/game")); + } catch (MalformedURLException ignored) {} + + // Implements support for launching in to multiplayer on classic servers using a mpticket + // file generated by an external program and stored in the instance's root folder. + Path mpticketFile = Paths.get(System.getProperty("user.dir") + "/../mpticket"); + Path mpticketFileCorrupt = Paths.get(System.getProperty("user.dir") + "/../mpticket.corrupt"); + + if (Files.exists(mpticketFile)) { + try (Scanner fileScanner = new Scanner( + Files.newInputStream(mpticketFile), + StandardCharsets.US_ASCII.name() + )) { + String[] mpticketParams = new String[3]; + + for (int i = 0; i < mpticketParams.length; i++) { + if (fileScanner.hasNextLine()) { + mpticketParams[i] = fileScanner.nextLine(); + } else { + Files.move( + mpticketFile, + mpticketFileCorrupt, + StandardCopyOption.REPLACE_EXISTING + ); + + throw new IllegalArgumentException("Mpticket file is corrupted!"); + } + } + + Files.delete(mpticketFile); + + // Assumes parameters are valid and in the correct order + appletWrap.setParameter("server", mpticketParams[0]); + appletWrap.setParameter("port", mpticketParams[1]); + appletWrap.setParameter("mppass", mpticketParams[2]); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Unable to read mpticket file!", e); + } + } + + if (serverAddress != null) { + appletWrap.setParameter("server", serverAddress); + appletWrap.setParameter("port", serverPort); + } + + appletWrap.setParameter("username", user); + appletWrap.setParameter("sessionid", session); + appletWrap.setParameter("stand-alone", "true"); // Show the quit button. + appletWrap.setParameter("haspaid", "true"); // Some old versions need this for world saves to work. + appletWrap.setParameter("demo", "false"); + appletWrap.setParameter("fullscreen", "false"); + + mcApplet.setStub(appletWrap); + + add(appletWrap); + + appletWrap.setPreferredSize(new Dimension(winSizeW, winSizeH)); + + pack(); + + setLocationRelativeTo(null); + setResizable(true); + + if (maximize) + this.setExtendedState(MAXIMIZED_BOTH); + + validate(); + + appletWrap.init(); + appletWrap.start(); + + setVisible(true); + } + + private final class ForceExitHandler extends WindowAdapter { + + @Override + public void windowClosing(WindowEvent e) { + new Thread(() -> { + try { + Thread.sleep(30000L); + } catch (InterruptedException localInterruptedException) { + localInterruptedException.printStackTrace(); + } + + LOGGER.info("Forcing exit!"); + + System.exit(0); + }).start(); + + if (appletWrap != null) { + appletWrap.stop(); + appletWrap.destroy(); + } + + // old minecraft versions can hang without this >_< + System.exit(0); + } + + } + +} diff --git a/libraries/launcher/org/multimc/NotFoundException.java b/libraries/launcher/org/multimc/exception/ParameterNotFoundException.java similarity index 73% rename from libraries/launcher/org/multimc/NotFoundException.java rename to libraries/launcher/org/multimc/exception/ParameterNotFoundException.java index ba12951d..9edbb826 100644 --- a/libraries/launcher/org/multimc/NotFoundException.java +++ b/libraries/launcher/org/multimc/exception/ParameterNotFoundException.java @@ -14,8 +14,12 @@ * limitations under the License. */ -package org.multimc; +package org.multimc.exception; + +public final class ParameterNotFoundException extends IllegalArgumentException { + + public ParameterNotFoundException(String key) { + super("Unknown parameter name: " + key); + } -public class NotFoundException extends Exception -{ } diff --git a/libraries/launcher/org/multimc/ParseException.java b/libraries/launcher/org/multimc/exception/ParseException.java similarity index 81% rename from libraries/launcher/org/multimc/ParseException.java rename to libraries/launcher/org/multimc/exception/ParseException.java index 7ea44c1f..c9a4c856 100644 --- a/libraries/launcher/org/multimc/ParseException.java +++ b/libraries/launcher/org/multimc/exception/ParseException.java @@ -14,12 +14,16 @@ * limitations under the License. */ -package org.multimc; +package org.multimc.exception; + +public final class ParseException extends IllegalArgumentException { + + public ParseException() { + super(); + } -public class ParseException extends java.lang.Exception -{ - public ParseException() { super(); } public ParseException(String message) { super(message); } + } diff --git a/libraries/launcher/org/multimc/impl/OneSixLauncher.java b/libraries/launcher/org/multimc/impl/OneSixLauncher.java new file mode 100644 index 00000000..d2596a69 --- /dev/null +++ b/libraries/launcher/org/multimc/impl/OneSixLauncher.java @@ -0,0 +1,183 @@ +/* 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.multimc.impl; + +import org.multimc.Launcher; +import org.multimc.applet.LegacyFrame; +import org.multimc.utils.ParamBucket; +import org.multimc.utils.Utils; + +import java.applet.Applet; +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +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(ParamBucket params) { + classLoader = ClassLoader.getSystemClassLoader(); + + mcParams = params.allSafe("param", Collections.emptyList()); + mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); + appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); + traits = params.allSafe("traits", Collections.emptyList()); + + 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", "854x480"); + + 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); + } + } + + 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); + + mcWindow.start( + mcApplet, + userName, + sessionId, + winSizeW, + winSizeH, + maximize, + serverAddress, + serverPort + ); + + return; + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Applet wrapper failed: ", e); + + LOGGER.warning("Falling back to using main class."); + } + } + + invokeMain(minecraftClass); + } + + 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/multimc/onesix/OneSixLauncher.java b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java deleted file mode 100644 index 0058bd43..00000000 --- a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java +++ /dev/null @@ -1,256 +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.multimc.onesix; - -import org.multimc.*; - -import java.applet.Applet; -import java.io.File; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class OneSixLauncher implements Launcher -{ - - private static final Logger LOGGER = Logger.getLogger("OneSixLauncher"); - - // parameters, separated from ParamBucket - private List libraries; - private List mcparams; - private List mods; - private List jarmods; - private List coremods; - private List traits; - private String appletClass; - private String mainClass; - private String nativePath; - private String userName, sessionId; - private String windowTitle; - private String windowParams; - - // secondary parameters - private int winSizeW; - private int winSizeH; - private boolean maximize; - private String cwd; - - private String serverAddress; - private String serverPort; - - // the much abused system classloader, for convenience (for further abuse) - private ClassLoader cl; - - private void processParams(ParamBucket params) throws NotFoundException - { - libraries = params.all("cp"); - 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()); - nativePath = params.first("natives"); - - userName = params.first("userName"); - sessionId = params.first("sessionId"); - windowTitle = params.firstSafe("windowTitle", "Minecraft"); - windowParams = params.firstSafe("windowParams", "854x480"); - - serverAddress = params.firstSafe("serverAddress", null); - serverPort = params.firstSafe("serverPort", null); - - cwd = System.getProperty("user.dir"); - - winSizeW = 854; - winSizeH = 480; - maximize = false; - - String[] dimStrings = windowParams.split("x"); - - if (windowParams.equalsIgnoreCase("max")) - { - maximize = true; - } - else if (dimStrings.length == 2) - { - try - { - winSizeW = Integer.parseInt(dimStrings[0]); - winSizeH = Integer.parseInt(dimStrings[1]); - } catch (NumberFormatException ignored) {} - } - } - - int legacyLaunch() - { - // Get the Minecraft Class and set the base folder - Class mc; - try - { - mc = cl.loadClass(mainClass); - - Field f = Utils.getMCPathField(mc); - - if (f == null) - { - LOGGER.warning("Could not find Minecraft path field."); - } - else - { - f.setAccessible(true); - f.set(null, new File(cwd)); - } - } catch (Exception e) - { - LOGGER.log( - Level.SEVERE, - "Could not set base folder. Failed to find/access Minecraft main class:", - e - ); - - return -1; - } - - System.setProperty("minecraft.applet.TargetDirectory", cwd); - - if(!traits.contains("noapplet")) - { - LOGGER.info("Launching with applet wrapper..."); - try - { - Class MCAppletClass = cl.loadClass(appletClass); - Applet mcappl = (Applet) MCAppletClass.newInstance(); - LegacyFrame mcWindow = new LegacyFrame(windowTitle); - mcWindow.start(mcappl, userName, sessionId, winSizeW, winSizeH, maximize, serverAddress, serverPort); - return 0; - } catch (Exception e) - { - LOGGER.log(Level.SEVERE, "Applet wrapper failed:", e); - - LOGGER.warning("Falling back to using main class."); - } - } - - // init params for the main method to chomp on. - String[] paramsArray = mcparams.toArray(new String[mcparams.size()]); - try - { - mc.getMethod("main", String[].class).invoke(null, (Object) paramsArray); - return 0; - } catch (Exception e) - { - LOGGER.log(Level.SEVERE, "Failed to invoke the Minecraft main class:", e); - - return -1; - } - } - - int launchWithMainClass() - { - // window size, title and state, onesix - if (maximize) - { - // FIXME: there is no good way to maximize the minecraft window in onesix. - // the following often breaks linux screen setups - // mcparams.add("--fullscreen"); - } - else - { - 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); - } - - // Get the Minecraft Class. - Class mc; - try - { - mc = cl.loadClass(mainClass); - } catch (ClassNotFoundException e) - { - LOGGER.log(Level.SEVERE, "Failed to find Minecraft main class:", e); - - return -1; - } - - // get the main method. - Method meth; - try - { - meth = mc.getMethod("main", String[].class); - } catch (NoSuchMethodException e) - { - LOGGER.log(Level.SEVERE, "Failed to acquire the main method:", e); - - return -1; - } - - // init params for the main method to chomp on. - String[] paramsArray = mcparams.toArray(new String[mcparams.size()]); - try - { - // static method doesn't have an instance - meth.invoke(null, (Object) paramsArray); - } catch (Exception e) - { - LOGGER.log(Level.SEVERE, "Failed to start Minecraft:", e); - - return -1; - } - return 0; - } - - @Override - public int launch(ParamBucket params) - { - // get and process the launch script params - try - { - processParams(params); - } catch (NotFoundException e) - { - LOGGER.log(Level.SEVERE, "Not enough arguments!"); - - return -1; - } - - // grab the system classloader and ... - cl = ClassLoader.getSystemClassLoader(); - - if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch") ) - { - // legacy launch uses the applet wrapper - return legacyLaunch(); - } - else - { - // normal launch just calls main() - return launchWithMainClass(); - } - } - -} diff --git a/libraries/launcher/org/multimc/ParamBucket.java b/libraries/launcher/org/multimc/utils/ParamBucket.java similarity index 68% rename from libraries/launcher/org/multimc/ParamBucket.java rename to libraries/launcher/org/multimc/utils/ParamBucket.java index 8ff03ddc..26ff8eef 100644 --- a/libraries/launcher/org/multimc/ParamBucket.java +++ b/libraries/launcher/org/multimc/utils/ParamBucket.java @@ -14,36 +14,34 @@ * limitations under the License. */ -package org.multimc; +package org.multimc.utils; + +import org.multimc.exception.ParameterNotFoundException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -public class ParamBucket -{ +public final class ParamBucket { private final Map> paramsMap = new HashMap<>(); - public void add(String key, String value) - { + public void add(String key, String value) { paramsMap.computeIfAbsent(key, k -> new ArrayList<>()) .add(value); } - public List all(String key) throws NotFoundException - { + public List all(String key) throws ParameterNotFoundException { List params = paramsMap.get(key); if (params == null) - throw new NotFoundException(); + throw new ParameterNotFoundException(key); return params; } - public List allSafe(String key, List def) - { + public List allSafe(String key, List def) { List params = paramsMap.get(key); if (params == null || params.isEmpty()) @@ -52,23 +50,16 @@ public class ParamBucket return params; } - public List allSafe(String key) - { - return allSafe(key, new ArrayList<>()); - } - - public String first(String key) throws NotFoundException - { + public String first(String key) throws ParameterNotFoundException { List list = all(key); if (list.isEmpty()) - throw new NotFoundException(); + throw new ParameterNotFoundException(key); return list.get(0); } - public String firstSafe(String key, String def) - { + public String firstSafe(String key, String def) { List params = paramsMap.get(key); if (params == null || params.isEmpty()) @@ -77,9 +68,4 @@ public class ParamBucket return params.get(0); } - public String firstSafe(String key) - { - return firstSafe(key, ""); - } - } diff --git a/libraries/launcher/org/multimc/utils/Utils.java b/libraries/launcher/org/multimc/utils/Utils.java new file mode 100644 index 00000000..416eff26 --- /dev/null +++ b/libraries/launcher/org/multimc/utils/Utils.java @@ -0,0 +1,49 @@ +/* + * 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.multimc.utils; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +public final class Utils { + + private Utils() {} + + /** + * Finds a field that looks like a Minecraft base folder in a supplied class + * + * @param clazz the class to scan + */ + public static Field getMinecraftBaseDirField(Class clazz) { + for (Field f : clazz.getDeclaredFields()) { + // Has to be File + if (f.getType() != File.class) + continue; + + // And Private Static. + if (!Modifier.isStatic(f.getModifiers()) || !Modifier.isPrivate(f.getModifiers())) + continue; + + return f; + } + + return null; + } + +} +