Merge pull request #520 from icelimetea/refactor-java-launcher-v2

This commit is contained in:
Sefa Eyeoglu 2022-05-21 12:06:45 +02:00 committed by GitHub
commit 2646ae29f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 607 additions and 680 deletions

View File

@ -4,17 +4,18 @@ find_package(Java 1.7 REQUIRED COMPONENTS Development)
include(UseJava) include(UseJava)
set(CMAKE_JAVA_JAR_ENTRY_POINT org.multimc.EntryPoint) set(CMAKE_JAVA_JAR_ENTRY_POINT org.multimc.EntryPoint)
set(CMAKE_JAVA_COMPILE_FLAGS -target 8 -source 8 -Xlint:deprecation -Xlint:unchecked) set(CMAKE_JAVA_COMPILE_FLAGS -target 7 -source 7 -Xlint:deprecation -Xlint:unchecked)
set(SRC set(SRC
org/multimc/EntryPoint.java org/multimc/EntryPoint.java
org/multimc/Launcher.java org/multimc/Launcher.java
org/multimc/LegacyFrame.java org/multimc/LauncherFactory.java
org/multimc/NotFoundException.java org/multimc/impl/OneSixLauncher.java
org/multimc/ParamBucket.java org/multimc/applet/LegacyFrame.java
org/multimc/ParseException.java org/multimc/exception/ParameterNotFoundException.java
org/multimc/Utils.java org/multimc/exception/ParseException.java
org/multimc/onesix/OneSixLauncher.java org/multimc/utils/Parameters.java
org/multimc/utils/Utils.java
net/minecraft/Launcher.java net/minecraft/Launcher.java
) )
add_jar(NewLaunch ${SRC}) add_jar(NewLaunch ${SRC})

View File

@ -16,31 +16,28 @@
package net.minecraft; 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.Applet;
import java.applet.AppletStub; import java.applet.AppletStub;
import java.awt.*;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.TreeMap;
public final class Launcher extends Applet implements AppletStub {
private final Map<String, String> params = new TreeMap<>();
private final Applet wrappedApplet;
public class Launcher extends Applet implements AppletStub
{
private Applet wrappedApplet;
private URL documentBase;
private boolean active = false; 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) {
this.setLayout(new BorderLayout()); this.setLayout(new BorderLayout());
this.add(applet, "Center"); this.add(applet, "Center");
this.wrappedApplet = applet; this.wrappedApplet = applet;
this.documentBase = documentBase;
} }
public void setParameter(String name, String value) public void setParameter(String name, String value)
@ -48,84 +45,61 @@ public class Launcher extends Applet implements AppletStub
params.put(name, value); params.put(name, value);
} }
public void replace(Applet applet)
{
this.wrappedApplet = applet;
applet.setStub(this);
applet.setSize(getWidth(), getHeight());
this.setLayout(new BorderLayout());
this.add(applet, "Center");
applet.init();
active = true;
applet.start();
validate();
}
@Override @Override
public String getParameter(String name) public String getParameter(String name) {
{
String param = params.get(name); String param = params.get(name);
if (param != null) if (param != null)
return param; return param;
try
{ try {
return super.getParameter(name); return super.getParameter(name);
} catch (Exception ignore){} } catch (Exception ignore) {}
return null; return null;
} }
@Override @Override
public boolean isActive() public boolean isActive() {
{
return active; return active;
} }
@Override @Override
public void appletResize(int width, int height) public void appletResize(int width, int height) {
{
wrappedApplet.resize(width, height); wrappedApplet.resize(width, height);
} }
@Override @Override
public void resize(int width, int height) public void resize(int width, int height) {
{
wrappedApplet.resize(width, height); wrappedApplet.resize(width, height);
} }
@Override @Override
public void resize(Dimension d) public void resize(Dimension d) {
{
wrappedApplet.resize(d); wrappedApplet.resize(d);
} }
@Override @Override
public void init() public void init() {
{
if (wrappedApplet != null) if (wrappedApplet != null)
{
wrappedApplet.init(); wrappedApplet.init();
}
} }
@Override @Override
public void start() public void start() {
{
wrappedApplet.start(); wrappedApplet.start();
active = true; active = true;
} }
@Override @Override
public void stop() public void stop() {
{
wrappedApplet.stop(); wrappedApplet.stop();
active = false; active = false;
} }
public void destroy() public void destroy() {
{
wrappedApplet.destroy(); wrappedApplet.destroy();
} }
@ -136,34 +110,34 @@ public class Launcher extends Applet implements AppletStub
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;
} }
@Override @Override
public URL getDocumentBase() public URL getDocumentBase() {
{
try { try {
// Special case only for Classic versions // Special case only for Classic versions
if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang")) { if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang"))
return new URL("http", "www.minecraft.net", 80, "/game/", null); return new URL("http", "www.minecraft.net", 80, "/game/");
}
return new URL("http://www.minecraft.net/game/"); return new URL("http://www.minecraft.net/game/");
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;
} }
@Override @Override
public void setVisible(boolean b) public void setVisible(boolean b) {
{
super.setVisible(b); super.setVisible(b);
wrappedApplet.setVisible(b); wrappedApplet.setVisible(b);
} }
public void update(Graphics paramGraphics)
{ public void update(Graphics paramGraphics) {}
}
public void paint(Graphics paramGraphics) public void paint(Graphics paramGraphics) {}
{
} }
}

View File

@ -1,20 +1,42 @@
package org.multimc;/* // SPDX-License-Identifier: GPL-3.0-only
* Copyright 2012-2021 MultiMC Contributors /*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 icelimetea, <fr3shtea@outlook.com>
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU General Public License as published by
* You may obtain a copy of the License at * the Free Software Foundation, version 3.
* *
* http://www.apache.org/licenses/LICENSE-2.0 * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* *
* Unless required by applicable law or agreed to in writing, software * You should have received a copy of the GNU General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and * This file incorporates work covered by the following copyright and
* limitations under the License. * permission notice:
*
* Copyright 2013-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 org.multimc.onesix.OneSixLauncher; package org.multimc;
import org.multimc.exception.ParseException;
import org.multimc.utils.Parameters;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
@ -23,31 +45,25 @@ import java.nio.charset.StandardCharsets;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public class EntryPoint public final class EntryPoint {
{
private static final Logger LOGGER = Logger.getLogger("EntryPoint"); private static final Logger LOGGER = Logger.getLogger("EntryPoint");
private final ParamBucket params = new ParamBucket(); private final Parameters params = new Parameters();
private org.multimc.Launcher launcher; public static void main(String[] args) {
public static void main(String[] args)
{
EntryPoint listener = new EntryPoint(); EntryPoint listener = new EntryPoint();
int retCode = listener.listen(); int retCode = listener.listen();
if (retCode != 0) if (retCode != 0) {
{
LOGGER.info("Exiting with " + retCode); LOGGER.info("Exiting with " + retCode);
System.exit(retCode); System.exit(retCode);
} }
} }
private Action parseLine(String inData) throws ParseException private Action parseLine(String inData) throws ParseException {
{
String[] tokens = inData.split("\\s+", 2); String[] tokens = inData.split("\\s+", 2);
if (tokens.length == 0) if (tokens.length == 0)
@ -62,21 +78,6 @@ public class EntryPoint
return Action.Abort; return Action.Abort;
} }
case "launcher": {
if (tokens.length != 2)
throw new ParseException("Expected 2 tokens, got " + tokens.length);
if (tokens[1].equals("onesix")) {
launcher = new OneSixLauncher();
LOGGER.info("Using onesix launcher.");
return Action.Proceed;
} else {
throw new ParseException("Invalid launcher type: " + tokens[1]);
}
}
default: { default: {
if (tokens.length != 2) if (tokens.length != 2)
throw new ParseException("Error while parsing:" + inData); throw new ParseException("Error while parsing:" + inData);
@ -88,8 +89,7 @@ public class EntryPoint
} }
} }
public int listen() public int listen() {
{
Action action = Action.Proceed; Action action = Action.Proceed;
try (BufferedReader reader = new BufferedReader(new InputStreamReader( try (BufferedReader reader = new BufferedReader(new InputStreamReader(
@ -112,21 +112,30 @@ public class EntryPoint
} }
// Main loop // Main loop
if (action == Action.Abort) if (action == Action.Abort) {
{
LOGGER.info("Launch aborted by the launcher."); LOGGER.info("Launch aborted by the launcher.");
return 1; return 1;
} }
if (launcher != null) try {
{ Launcher launcher =
return launcher.launch(params); LauncherFactory
.getInstance()
.createLauncher(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.");
return 1;
} }
private enum Action { private enum Action {

View File

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

View File

@ -0,0 +1,63 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 icelimetea, <fr3shtea@outlook.com>
*
* 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
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.multimc;
import org.multimc.impl.OneSixLauncher;
import org.multimc.utils.Parameters;
import java.util.HashMap;
import java.util.Map;
public final class LauncherFactory {
private static final LauncherFactory INSTANCE = new LauncherFactory();
private final Map<String, LauncherProvider> launcherRegistry = new HashMap<>();
private LauncherFactory() {
launcherRegistry.put("onesix", new LauncherProvider() {
@Override
public Launcher provide(Parameters parameters) {
return new OneSixLauncher(parameters);
}
});
}
public Launcher createLauncher(Parameters parameters) {
String name = parameters.first("launcher");
LauncherProvider launcherProvider = launcherRegistry.get(name);
if (launcherProvider == null)
throw new IllegalArgumentException("Invalid launcher type: " + name);
return launcherProvider.provide(parameters);
}
public static LauncherFactory getInstance() {
return INSTANCE;
}
public interface LauncherProvider {
Launcher provide(Parameters parameters);
}
}

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,162 @@
/*
* 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.applet;
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.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.List;
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 final Launcher appletWrap;
public LegacyFrame(String title, Applet mcApplet) {
super(title);
appletWrap = new Launcher(mcApplet);
mcApplet.setStub(appletWrap);
try {
setIconImage(ImageIO.read(new File("icon.png")));
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Unable to read Minecraft icon!", e);
}
addWindowListener(new ForceExitHandler());
}
public void start (
String user,
String session,
int winSizeW,
int winSizeH,
boolean maximize,
String serverAddress,
String serverPort
) {
// 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 {
List<String> lines = Files.readAllLines(mpticketFile, StandardCharsets.UTF_8);
if (lines.size() < 3) {
Files.move(
mpticketFile,
mpticketFileCorrupt,
StandardCopyOption.REPLACE_EXISTING
);
LOGGER.warning("Mpticket file is corrupted!");
} else {
// Assumes parameters are valid and in the correct order
appletWrap.setParameter("server", lines.get(0));
appletWrap.setParameter("port", lines.get(1));
appletWrap.setParameter("mppass", lines.get(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");
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(new Runnable() {
@Override
public void run() {
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. * 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,12 @@
* limitations under the License. * limitations under the License.
*/ */
package org.multimc; package org.multimc.exception;
public final class ParseException extends IllegalArgumentException {
public class ParseException extends java.lang.Exception
{
public ParseException() { super(); }
public ParseException(String message) { public ParseException(String message) {
super(message); super(message);
} }
} }

View File

@ -0,0 +1,189 @@
/* 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.Parameters;
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(Parameters params) {
classLoader = ClassLoader.getSystemClassLoader();
mcParams = params.allSafe("param", Collections.<String>emptyList());
mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft");
appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet");
traits = params.allSafe("traits", Collections.<String>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", 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
);
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();
}
}
}

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,41 @@
* limitations under the License. * limitations under the License.
*/ */
package org.multimc; package org.multimc.utils;
import org.multimc.exception.ParameterNotFoundException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class ParamBucket public final class Parameters {
{
private final Map<String, List<String>> paramsMap = new HashMap<>(); private final Map<String, List<String>> paramsMap = new HashMap<>();
public void add(String key, String value) public void add(String key, String value) {
{ List<String> params = paramsMap.get(key);
paramsMap.computeIfAbsent(key, k -> new ArrayList<>())
.add(value); if (params == null) {
params = new ArrayList<>();
paramsMap.put(key, params);
}
params.add(value);
} }
public List<String> all(String key) throws NotFoundException public List<String> all(String key) throws ParameterNotFoundException {
{
List<String> params = paramsMap.get(key); List<String> params = paramsMap.get(key);
if (params == null) if (params == null)
throw new NotFoundException(); throw new ParameterNotFoundException(key);
return params; 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); List<String> params = paramsMap.get(key);
if (params == null || params.isEmpty()) if (params == null || params.isEmpty())
@ -52,23 +57,16 @@ public class ParamBucket
return params; return params;
} }
public List<String> allSafe(String key) public String first(String key) throws ParameterNotFoundException {
{
return allSafe(key, new ArrayList<>());
}
public String first(String key) throws NotFoundException
{
List<String> list = all(key); List<String> list = all(key);
if (list.isEmpty()) if (list.isEmpty())
throw new NotFoundException(); throw new ParameterNotFoundException(key);
return list.get(0); return list.get(0);
} }
public String firstSafe(String key, String def) public String firstSafe(String key, String def) {
{
List<String> params = paramsMap.get(key); List<String> params = paramsMap.get(key);
if (params == null || params.isEmpty()) if (params == null || params.isEmpty())
@ -77,9 +75,4 @@ public class ParamBucket
return params.get(0); 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;
}
}