diff --git a/depends/launcher/CMakeLists.txt b/depends/launcher/CMakeLists.txt index 804e93ae7..7564161d4 100644 --- a/depends/launcher/CMakeLists.txt +++ b/depends/launcher/CMakeLists.txt @@ -18,20 +18,17 @@ set(SRC # The launcher has to be there for silly FML/Forge relauncher. net/minecraft/Launcher.java org/multimc/legacy/LegacyLauncher.java + org/multimc/LegacyFrame.java # onesix launcher org/multimc/onesix/OneSixLauncher.java - org/multimc/onesix/MMCClassLoader.java # generic launcher 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/IconLoader.java ) add_jar(NewLaunch ${SRC}) diff --git a/depends/launcher/org/multimc/EntryPoint.java b/depends/launcher/org/multimc/EntryPoint.java index b8cc6c412..d1fc54a84 100644 --- a/depends/launcher/org/multimc/EntryPoint.java +++ b/depends/launcher/org/multimc/EntryPoint.java @@ -102,6 +102,7 @@ public class EntryPoint } m_params.add(command, param); + //System.out.println(command + " : " + param); return Action.Proceed; } diff --git a/depends/launcher/org/multimc/IconLoader.java b/depends/launcher/org/multimc/IconLoader.java new file mode 100644 index 000000000..f1638f3a6 --- /dev/null +++ b/depends/launcher/org/multimc/IconLoader.java @@ -0,0 +1,132 @@ +package org.multimc; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; + +/***************************************************************************** + * A convenience class for loading icons from images. + * + * Icons loaded from this class are formatted to fit within the required + * dimension (16x16, 32x32, or 128x128). If the source image is larger than the + * target dimension, it is shrunk down to the minimum size that will fit. If it + * is smaller, then it is only scaled up if the new scale can be a per-pixel + * linear scale (i.e., x2, x3, x4, etc). In both cases, the image's width/height + * ratio is kept the same as the source image. + * + * @author Chris Molini + *****************************************************************************/ +public class IconLoader +{ + /************************************************************************* + * Loads an icon in ByteBuffer form. + * + * @param filepath + * The location of the Image to use as an icon. + * + * @return An array of ByteBuffers containing the pixel data for the icon in + * various sizes (as recommended by the OS). + *************************************************************************/ + public static ByteBuffer[] load(String filepath) + { + BufferedImage image; + try { + image = ImageIO.read ( new File( filepath ) ); + } catch ( IOException e ) { + e.printStackTrace(); + return new ByteBuffer[0]; + } + ByteBuffer[] buffers; + buffers = new ByteBuffer[1]; + buffers[0] = loadInstance(image, 128); + return buffers; + } + + /************************************************************************* + * Copies the supplied image into a square icon at the indicated size. + * + * @param image + * The image to place onto the icon. + * @param dimension + * The desired size of the icon. + * + * @return A ByteBuffer of pixel data at the indicated size. + *************************************************************************/ + private static ByteBuffer loadInstance(BufferedImage image, int dimension) + { + BufferedImage scaledIcon = new BufferedImage(dimension, dimension, + BufferedImage.TYPE_INT_ARGB_PRE); + Graphics2D g = scaledIcon.createGraphics(); + double ratio = getIconRatio(image, scaledIcon); + double width = image.getWidth() * ratio; + double height = image.getHeight() * ratio; + g.drawImage(image, (int) ((scaledIcon.getWidth() - width) / 2), + (int) ((scaledIcon.getHeight() - height) / 2), (int) (width), + (int) (height), null); + g.dispose(); + + return convertToByteBuffer(scaledIcon); + } + + /************************************************************************* + * Gets the width/height ratio of the icon. This is meant to simplify + * scaling the icon to a new dimension. + * + * @param src + * The base image that will be placed onto the icon. + * @param icon + * The icon that will have the image placed on it. + * + * @return The amount to scale the source image to fit it onto the icon + * appropriately. + *************************************************************************/ + private static double getIconRatio(BufferedImage src, BufferedImage icon) + { + double ratio = 1; + if (src.getWidth() > icon.getWidth()) + ratio = (double) (icon.getWidth()) / src.getWidth(); + else + ratio = (int) (icon.getWidth() / src.getWidth()); + if (src.getHeight() > icon.getHeight()) + { + double r2 = (double) (icon.getHeight()) / src.getHeight(); + if (r2 < ratio) + ratio = r2; + } + else + { + double r2 = (int) (icon.getHeight() / src.getHeight()); + if (r2 < ratio) + ratio = r2; + } + return ratio; + } + + /************************************************************************* + * Converts a BufferedImage into a ByteBuffer of pixel data. + * + * @param image + * The image to convert. + * + * @return A ByteBuffer that contains the pixel data of the supplied image. + *************************************************************************/ + public static ByteBuffer convertToByteBuffer(BufferedImage image) + { + byte[] buffer = new byte[image.getWidth() * image.getHeight() * 4]; + int counter = 0; + for (int i = 0; i < image.getHeight(); i++) + for (int j = 0; j < image.getWidth(); j++) + { + int colorSpace = image.getRGB(j, i); + buffer[counter + 0] = (byte) ((colorSpace << 8) >> 24); + buffer[counter + 1] = (byte) ((colorSpace << 16) >> 24); + buffer[counter + 2] = (byte) ((colorSpace << 24) >> 24); + buffer[counter + 3] = (byte) (colorSpace >> 24); + counter += 4; + } + return ByteBuffer.wrap(buffer); + } +} \ No newline at end of file diff --git a/depends/launcher/org/multimc/Utils.java b/depends/launcher/org/multimc/Utils.java index c850f96d6..1077065a9 100644 --- a/depends/launcher/org/multimc/Utils.java +++ b/depends/launcher/org/multimc/Utils.java @@ -34,6 +34,110 @@ import java.util.zip.ZipFile; 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(); + } + + /** + * Adds the specified library to the classpath + * + * @param s the path to add + * @throws Exception + */ + public static void addToClassPath(String s) throws Exception + { + File f = new File(s); + URL u = f.toURI().toURL(); + URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); + Class urlClass = URLClassLoader.class; + Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class}); + method.setAccessible(true); + method.invoke(urlClassLoader, new Object[]{u}); + } + + /** + * Adds many libraries to the classpath + * + * @param jars the paths to add + */ + public static boolean addToClassPath(List jars) + { + boolean pure = true; + // initialize the class path + for (String jar : jars) + { + try + { + Utils.addToClassPath(jar); + } catch (Exception e) + { + System.err.println("Unable to load: " + jar); + e.printStackTrace(System.err); + pure = false; + } + } + return pure; + } + + /** + * Adds the specified path to the java library path + * + * @param pathToAdd the path to add + * @throws Exception + */ + @Deprecated + public static void addLibraryPath(String pathToAdd) throws Exception + { + final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths"); + usrPathsField.setAccessible(true); + + //get array of paths + final String[] paths = (String[]) usrPathsField.get(null); + + //check if the path to add is already present + for (String path : paths) + { + if (path.equals(pathToAdd)) + { + return; + } + } + + //add the new path + final String[] newPaths = Arrays.copyOf(paths, paths.length + 1); + newPaths[newPaths.length - 1] = pathToAdd; + usrPathsField.set(null, newPaths); + } + /** * Finds a field that looks like a Minecraft base folder in a supplied class * diff --git a/depends/launcher/org/multimc/onesix/MMCClassLoader.java b/depends/launcher/org/multimc/onesix/MMCClassLoader.java deleted file mode 100644 index 6c768ffe4..000000000 --- a/depends/launcher/org/multimc/onesix/MMCClassLoader.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.multimc.onesix; - -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Arrays; -import java.util.List; - -public class MMCClassLoader extends URLClassLoader -{ - public MMCClassLoader(String natives, List allJars) - throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, - InvocationTargetException, IllegalAccessException, NoSuchFieldException - { - super(process(allJars)); - Method setProperty = loadClass("java.lang.System").getMethod("setProperty", String.class, String.class); - setProperty.invoke(null, "java.library.path", natives); - setProperty.invoke(null, "org.lwjgl.librarypath", natives); - setProperty.invoke(null, "net.java.games.input.librarypath", natives); - } - - private static URL[] process(List allJars) throws MalformedURLException - { - URL[] urls = new URL[allJars.size()]; - for (int i = 0; i < allJars.size(); i++) - { - String jar = allJars.get(i); - urls[i] = new File(jar).toURI().toURL(); - } - return urls; - } - - // TODO: use this method to use custom log configs - // @Override - // public URL findResource(String name) - // { - // return super.findResource(name); - // } -} diff --git a/depends/launcher/org/multimc/onesix/OneSixLauncher.java b/depends/launcher/org/multimc/onesix/OneSixLauncher.java index 6f9ce874c..8ef6376dd 100644 --- a/depends/launcher/org/multimc/onesix/OneSixLauncher.java +++ b/depends/launcher/org/multimc/onesix/OneSixLauncher.java @@ -18,8 +18,8 @@ package org.multimc.onesix; import org.multimc.*; import java.applet.Applet; -import java.awt.*; import java.io.File; +import java.awt.*; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -49,13 +49,13 @@ public class OneSixLauncher implements Launcher private String cwd; // the much abused system classloader, for convenience (for further abuse) - private MMCClassLoader cl; + private ClassLoader cl; private void processParams(ParamBucket params) throws NotFoundException { libraries = params.all("cp"); extlibs = params.all("ext"); - mcparams = params.allSafe("param", new ArrayList()); + mcparams = params.allSafe("param", new ArrayList() ); mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); mods = params.allSafe("mod", new ArrayList()); @@ -84,10 +84,7 @@ public class OneSixLauncher implements Launcher try { winSize = new Dimension(Integer.parseInt(dimStrings[0]), Integer.parseInt(dimStrings[1])); - } - catch (NumberFormatException ignored) - { - } + } catch (NumberFormatException ignored) {} } } @@ -120,7 +117,7 @@ public class OneSixLauncher implements Launcher } Utils.log(); - if (mods.size() > 0) + if(mods.size() > 0) { Utils.log("Mods:"); for (String s : mods) @@ -153,14 +150,10 @@ public class OneSixLauncher implements Launcher Utils.log("Params:"); Utils.log(" " + mcparams.toString()); Utils.log(); - if (maximize) - { + if(maximize) Utils.log("Window size: max (if available)"); - } else - { Utils.log("Window size: " + Integer.toString(winSize.width) + " x " + Integer.toString(winSize.height)); - } Utils.log(); } @@ -183,8 +176,7 @@ public class OneSixLauncher implements Launcher f.setAccessible(true); f.set(null, new File(cwd)); } - } - catch (Exception e) + } catch (Exception e) { System.err.println("Could not set base folder. Failed to find/access Minecraft main class:"); e.printStackTrace(System.err); @@ -201,11 +193,10 @@ public class OneSixLauncher implements Launcher try { Class MCAppletClass = cl.loadClass(appletClass); - Applet mcappl = (Applet)MCAppletClass.newInstance(); + Applet mcappl = (Applet) MCAppletClass.newInstance(); LegacyFrame mcWindow = new LegacyFrame(windowTitle); mcWindow.start(mcappl, userName, sessionId, winSize, maximize); - } - catch (Exception e) + } catch (Exception e) { Utils.log("Applet wrapper failed:", "Error"); e.printStackTrace(System.err); @@ -213,9 +204,8 @@ public class OneSixLauncher implements Launcher Utils.log("Falling back to compatibility mode."); try { - mc.getMethod("main", String[].class).invoke(null, (Object)mcArgs); - } - catch (Exception e1) + mc.getMethod("main", String[].class).invoke(null, (Object) mcArgs); + } catch (Exception e1) { Utils.log("Failed to invoke the Minecraft main class:", "Fatal"); e1.printStackTrace(System.err); @@ -247,8 +237,7 @@ public class OneSixLauncher implements Launcher try { mc = cl.loadClass(mainClass); - } - catch (ClassNotFoundException e) + } catch (ClassNotFoundException e) { System.err.println("Failed to find Minecraft main class:"); e.printStackTrace(System.err); @@ -260,22 +249,66 @@ public class OneSixLauncher implements Launcher try { meth = mc.getMethod("main", String[].class); - } - catch (NoSuchMethodException e) + } catch (NoSuchMethodException e) { System.err.println("Failed to acquire the main method:"); e.printStackTrace(System.err); return -1; } - + /* + final java.nio.ByteBuffer[] icons = IconLoader.load("icon.png"); + new Thread() { + public void run() { + ClassLoader cl = ClassLoader.getSystemClassLoader(); + try + { + Class Display; + Method isCreated; + Method setTitle; + Method setIcon; + Field fieldWindowCreated; + Boolean created = false; + Display = cl.loadClass("org.lwjgl.opengl.Display"); + fieldWindowCreated = Display.getDeclaredField("window_created"); + fieldWindowCreated.setAccessible( true ); + setTitle = Display.getMethod("setTitle", String.class); + setIcon = Display.getMethod("setIcon", java.nio.ByteBuffer[].class); + created = (Boolean) fieldWindowCreated.get( null ); + // set the window title? Maybe? + while(!created) + { + try + { + Thread.sleep(150); + created = (Boolean) fieldWindowCreated.get( null ); + } catch (InterruptedException ignored) {} + } + // Give it a bit more time ;) + Thread.sleep(150); + // set the title + setTitle.invoke(null,windowTitle); + // only set icon when there's actually something to set... + if(icons.length > 0) + { + setIcon.invoke(null,(Object)icons); + } + } + catch (Exception e) + { + System.err.println("Couldn't set window icon or title."); + e.printStackTrace(System.err); + } + } + } + .start(); + */ // 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) + meth.invoke(null, (Object) paramsArray); + } catch (Exception e) { System.err.println("Failed to start Minecraft:"); e.printStackTrace(System.err); @@ -284,29 +317,35 @@ public class OneSixLauncher implements Launcher return 0; } - @Override public int launch(ParamBucket params) + @Override + public int launch(ParamBucket params) { // get and process the launch script params try { processParams(params); - } - catch (NotFoundException e) + } catch (NotFoundException e) { System.err.println("Not enough arguments."); e.printStackTrace(System.err); return -1; } + // add libraries to classpath + if(!Utils.addToClassPath(libraries)) + { + System.err.println("Halting launch due to previous errors."); + return -1; + } + // print the pretty things printStats(); // extract native libs (depending on platform here... java!) Utils.log("Preparing native libraries..."); String property = System.getProperty("os.arch"); - boolean is_64 = - property.equalsIgnoreCase("x86_64") || property.equalsIgnoreCase("amd64"); - for (String extlib : extlibs) + boolean is_64 = property.equalsIgnoreCase("x86_64") || property.equalsIgnoreCase("amd64"); + for(String extlib: extlibs) { try { @@ -314,8 +353,7 @@ public class OneSixLauncher implements Launcher File cleanlibf = new File(cleanlib); Utils.log("Extracting " + cleanlibf.getName()); Utils.unzipNatives(cleanlibf, new File(natives)); - } - catch (IOException e) + } catch (IOException e) { System.err.println("Failed to extract native library:"); e.printStackTrace(System.err); @@ -324,44 +362,36 @@ public class OneSixLauncher implements Launcher } Utils.log(); + // set the native libs path... the brute force way try { - cl = new MMCClassLoader(natives, libraries); - } - catch (Exception e) + System.setProperty("java.library.path", natives); + System.setProperty("org.lwjgl.librarypath", natives); + System.setProperty("net.java.games.input.librarypath", natives); + // by the power of reflection, initialize native libs again. DIRTY! + // this is SO BAD. imagine doing that to ld + Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths"); + fieldSysPath.setAccessible( true ); + fieldSysPath.set( null, null ); + } catch (Exception e) { - e.printStackTrace(); + System.err.println("Failed to set the native library path:"); + e.printStackTrace(System.err); + return -1; } - final int[] result = {-1}; + // grab the system classloader and ... + cl = ClassLoader.getSystemClassLoader(); - // fix log4j by sticking it in a thread with custom contextclassloader - Thread t = new Thread("main") + if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch") ) { - @Override public void run() - { - if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch")) - { - // legacy launch uses the applet wrapper - result[0] = legacyLaunch(); - } - else - { - // normal launch just calls main() - result[0] = launchWithMainClass(); - } - } - }; - t.setContextClassLoader(cl); - t.start(); - try - { - t.join(); + // legacy launch uses the applet wrapper + return legacyLaunch(); } - catch (InterruptedException e) + else { - e.printStackTrace(); + // normal launch just calls main() + return launchWithMainClass(); } - return result[0]; } }