Refactor some parts of NewLaunch (part 2)

This commit is contained in:
icelimetea 2022-05-02 22:36:55 +01:00
parent d29720fbce
commit 8de63b60b1
14 changed files with 542 additions and 634 deletions

View File

@ -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})

View File

@ -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<String, String> 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<String, String> params;
public Launcher(Applet applet, URL documentBase)
{
params = new TreeMap<String, String>();
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)
{
}
}
public void update(Graphics paramGraphics) {}
public void paint(Graphics paramGraphics) {}
}

View File

@ -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.");

View File

@ -16,7 +16,8 @@
package org.multimc;
public interface Launcher
{
int launch(ParamBucket params);
public interface Launcher {
void launch() throws Exception;
}

View File

@ -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<String, Function<ParamBucket, Launcher>> launcherRegistry = new HashMap<>();
private LauncherFactory() {
launcherRegistry.put("onesix", OneSixLauncher::new);
}
public Launcher createLauncher(String name, ParamBucket parameters) {
Function<ParamBucket, Launcher> launcherCreator =
launcherRegistry.get(name);
if (launcherCreator == null)
throw new IllegalArgumentException("Invalid launcher type: " + name);
return launcherCreator.apply(parameters);
}
public static LauncherFactory getInstance() {
return INSTANCE;
}
}

View File

@ -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 ) {}
}

View File

@ -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<String> 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;
}
}

View File

@ -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);
}
}
}

View File

@ -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
{
}

View File

@ -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);
}
}

View File

@ -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<String> mcParams;
private final List<String> 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();
}
}
}

View File

@ -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<String> libraries;
private List<String> mcparams;
private List<String> mods;
private List<String> jarmods;
private List<String> coremods;
private List<String> 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<String>() );
mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft");
appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet");
traits = params.allSafe("traits", new ArrayList<String>());
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();
}
}
}

View File

@ -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<String, List<String>> 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<String> all(String key) throws NotFoundException
{
public List<String> all(String key) throws ParameterNotFoundException {
List<String> params = paramsMap.get(key);
if (params == null)
throw new NotFoundException();
throw new ParameterNotFoundException(key);
return params;
}
public List<String> allSafe(String key, List<String> def)
{
public List<String> allSafe(String key, List<String> def) {
List<String> params = paramsMap.get(key);
if (params == null || params.isEmpty())
@ -52,23 +50,16 @@ public class ParamBucket
return params;
}
public List<String> allSafe(String key)
{
return allSafe(key, new ArrayList<>());
}
public String first(String key) throws NotFoundException
{
public String first(String key) throws ParameterNotFoundException {
List<String> 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<String> 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, "");
}
}

View File

@ -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;
}
}