    001/*
    002 * Forge Mod Loader
    003 * Copyright (c) 2012-2013 cpw.
    004 * All rights reserved. This program and the accompanying materials
    005 * are made available under the terms of the GNU Lesser Public License v2.1
    006 * which accompanies this distribution, and is available at
    007 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
    008 *
    009 * Contributors:
    010 *     cpw - implementation
    011 */
    012
    013package cpw.mods.fml.relauncher;
    014
    015import java.applet.Applet;
    016import java.io.File;
    017import java.lang.reflect.Method;
    018import java.net.URLClassLoader;
    019
    020import javax.swing.JDialog;
    021import javax.swing.JOptionPane;
    022
    023public class FMLRelauncher
    024{
    025    private static FMLRelauncher INSTANCE;
    026    public static String logFileNamePattern;
    027    private static String side;
    028    private RelaunchClassLoader classLoader;
    029    private Object newApplet;
    030    private Class<? super Object> appletClass;
    031
    032    JDialog popupWindow;
    033
    034    public static void handleClientRelaunch(ArgsWrapper wrap)
    035    {
    036        logFileNamePattern = "ForgeModLoader-client-%g.log";
    037        side = "CLIENT";
    038        instance().relaunchClient(wrap);
    039    }
    040
    041    public static void handleServerRelaunch(ArgsWrapper wrap)
    042    {
    043        logFileNamePattern = "ForgeModLoader-server-%g.log";
    044        side = "SERVER";
    045        instance().relaunchServer(wrap);
    046    }
    047
    048    static FMLRelauncher instance()
    049    {
    050        if (INSTANCE == null)
    051        {
    052            INSTANCE = new FMLRelauncher();
    053        }
    054        return INSTANCE;
    055
    056    }
    057
    058    private FMLRelauncher()
    059    {
    060        URLClassLoader ucl = (URLClassLoader) getClass().getClassLoader();
    061
    062        classLoader = new RelaunchClassLoader(ucl.getURLs());
    063
    064    }
    065
    066    private void showWindow(boolean showIt)
    067    {
    068        if (RelaunchLibraryManager.downloadMonitor != null) { return; }
    069        try
    070        {
    071            if (showIt)
    072            {
    073                RelaunchLibraryManager.downloadMonitor = new Downloader();
    074                popupWindow = (JDialog) RelaunchLibraryManager.downloadMonitor.makeDialog();
    075            }
    076            else
    077            {
    078                RelaunchLibraryManager.downloadMonitor = new DummyDownloader();
    079            }
    080        }
    081        catch (Throwable e)
    082        {
    083            if (RelaunchLibraryManager.downloadMonitor == null)
    084            {
    085                RelaunchLibraryManager.downloadMonitor = new DummyDownloader();
    086                e.printStackTrace();
    087            }
    088            else
    089            {
    090                RelaunchLibraryManager.downloadMonitor.makeHeadless();
    091            }
    092            popupWindow = null;
    093        }
    094    }
    095
    096    private void relaunchClient(ArgsWrapper wrap)
    097    {
    098        showWindow(true);
    099        // Now we re-inject the home into the "new" minecraft under our control
    100        Class<? super Object> client;
    101        try
    102        {
    103            File minecraftHome = computeExistingClientHome();
    104            setupHome(minecraftHome);
    105
    106            client = setupNewClientHome(minecraftHome);
    107        }
    108        finally
    109        {
    110            if (popupWindow != null)
    111            {
    112                popupWindow.setVisible(false);
    113                popupWindow.dispose();
    114            }
    115        }
    116
    117        if (RelaunchLibraryManager.downloadMonitor.shouldStopIt())
    118        {
    119            System.exit(1);
    120        }
    121        try
    122        {
    123            ReflectionHelper.findMethod(client, null, new String[] { "fmlReentry" }, ArgsWrapper.class).invoke(null, wrap);
    124        }
    125        catch (Exception e)
    126        {
    127            e.printStackTrace();
    128            // Hmmm
    129        }
    130    }
    131
    132    private Class<? super Object> setupNewClientHome(File minecraftHome)
    133    {
    134        Class<? super Object> client = ReflectionHelper.getClass(classLoader, "net.minecraft.client.Minecraft");
    135        ReflectionHelper.setPrivateValue(client, null, minecraftHome,
    136                "field_" + "71463_am" /*Separate that so that MCP's updatenames does not replace it*/,
    137                "an", "minecraftDir");
    138        return client;
    139    }
    140
    141    private void relaunchServer(ArgsWrapper wrap)
    142    {
    143        showWindow(false);
    144        // Now we re-inject the home into the "new" minecraft under our control
    145        Class<? super Object> server;
    146        File minecraftHome = new File(".");
    147        setupHome(minecraftHome);
    148
    149        server = ReflectionHelper.getClass(classLoader, "net.minecraft.server.MinecraftServer");
    150        try
    151        {
    152            ReflectionHelper.findMethod(server, null, new String[] { "fmlReentry" }, ArgsWrapper.class).invoke(null, wrap);
    153        }
    154        catch (Exception e)
    155        {
    156            e.printStackTrace();
    157        }
    158    }
    159
    160    private void setupHome(File minecraftHome)
    161    {
    162        FMLInjectionData.build(minecraftHome, classLoader);
    163        FMLRelaunchLog.minecraftHome = minecraftHome;
    164        FMLRelaunchLog.info("Forge Mod Loader version %s.%s.%s.%s for Minecraft %s loading", FMLInjectionData.major, FMLInjectionData.minor,
    165                FMLInjectionData.rev, FMLInjectionData.build, FMLInjectionData.mccversion, FMLInjectionData.mcpversion);
    166        FMLRelaunchLog.info("Java is %s, version %s, running on %s:%s:%s, installed at %s", System.getProperty("java.vm.name"), System.getProperty("java.version"), System.getProperty("os.name"), System.getProperty("os.arch"), System.getProperty("os.version"), System.getProperty("java.home"));
    167        FMLRelaunchLog.fine("Java classpath at launch is %s", System.getProperty("java.class.path"));
    168        FMLRelaunchLog.fine("Java library path at launch is %s", System.getProperty("java.library.path"));
    169
    170        try
    171        {
    172            RelaunchLibraryManager.handleLaunch(minecraftHome, classLoader);
    173        }
    174        catch (Throwable t)
    175        {
    176            if (popupWindow != null)
    177            {
    178                try
    179                {
    180                    String logFile = new File(minecraftHome, "ForgeModLoader-client-0.log").getCanonicalPath();
    181                    JOptionPane.showMessageDialog(popupWindow, String.format(
    182                            "<html><div align=\"center\"><font size=\"+1\">There was a fatal error starting up minecraft and FML</font></div><br/>"
    183                                    + "Minecraft cannot launch in it's current configuration<br/>"
    184                                    + "Please consult the file <i><a href=\"file:///%s\">%s</a></i> for further information</html>", logFile, logFile),
    185                            "Fatal FML error", JOptionPane.ERROR_MESSAGE);
    186                }
    187                catch (Exception ex)
    188                {
    189                    // ah well, we tried
    190                }
    191            }
    192            throw new RuntimeException(t);
    193        }
    194    }
    195
    196    /**
    197     * @return the location of the client home
    198     */
    199    private File computeExistingClientHome()
    200    {
    201        Class<? super Object> mcMaster = ReflectionHelper.getClass(getClass().getClassLoader(), "net.minecraft.client.Minecraft");
    202        // If we get the system property we inject into the old MC, setup the
    203        // dir, then pull the value
    204        String str = System.getProperty("minecraft.applet.TargetDirectory");
    205        if (str != null)
    206        {
    207            str = str.replace('/', File.separatorChar);
    208            ReflectionHelper.setPrivateValue(mcMaster, null, new File(str), "minecraftDir", "an", "minecraftDir");
    209        }
    210        // We force minecraft to setup it's homedir very early on so we can
    211        // inject stuff into it
    212        Method setupHome = ReflectionHelper.findMethod(mcMaster, null, new String[] { "getMinecraftDir", "getMinecraftDir", "b" });
    213        try
    214        {
    215            setupHome.invoke(null);
    216        }
    217        catch (Exception e)
    218        {
    219            // Hmmm
    220        }
    221        File minecraftHome = ReflectionHelper.getPrivateValue(mcMaster, null, "minecraftDir", "an", "minecraftDir");
    222        return minecraftHome;
    223    }
    224
    225    public static void appletEntry(Applet minecraftApplet)
    226    {
    227        side = "CLIENT";
    228        logFileNamePattern = "ForgeModLoader-client-%g.log";
    229        instance().relaunchApplet(minecraftApplet);
    230    }
    231
    232    private void relaunchApplet(Applet minecraftApplet)
    233    {
    234        showWindow(true);
    235
    236        if (minecraftApplet.getClass().getClassLoader() == classLoader)
    237        {
    238            if (popupWindow != null)
    239            {
    240                popupWindow.setVisible(false);
    241                popupWindow.dispose();
    242            }
    243            try
    244            {
    245                newApplet = minecraftApplet;
    246                appletClass = ReflectionHelper.getClass(classLoader, "net.minecraft.client.MinecraftApplet");
    247                ReflectionHelper.findMethod(appletClass, newApplet, new String[] { "fmlInitReentry" }).invoke(newApplet);
    248                return;
    249            }
    250            catch (Exception e)
    251            {
    252                System.out.println("FMLRelauncher.relaunchApplet");
    253                e.printStackTrace();
    254                throw new RuntimeException(e);
    255            }
    256        }
    257
    258        File mcDir = computeExistingClientHome();
    259        setupHome(mcDir);
    260        setupNewClientHome(mcDir);
    261
    262        Class<? super Object> parentAppletClass = ReflectionHelper.getClass(getClass().getClassLoader(), "java.applet.Applet");
    263
    264        try
    265        {
    266            appletClass = ReflectionHelper.getClass(classLoader, "net.minecraft.client.MinecraftApplet");
    267            newApplet = appletClass.newInstance();
    268            Object appletContainer = ReflectionHelper.getPrivateValue(ReflectionHelper.getClass(getClass().getClassLoader(), "java.awt.Component"),
    269                    minecraftApplet, "parent");
    270
    271            String launcherClassName = System.getProperty("minecraft.applet.WrapperClass", "net.minecraft.Launcher");
    272            Class<? super Object> launcherClass = ReflectionHelper.getClass(getClass().getClassLoader(), launcherClassName);
    273            if (launcherClass.isInstance(appletContainer))
    274            {
    275                ReflectionHelper.findMethod(ReflectionHelper.getClass(getClass().getClassLoader(), "java.awt.Container"), minecraftApplet,
    276                        new String[] { "removeAll" }).invoke(appletContainer);
    277                ReflectionHelper.findMethod(launcherClass, appletContainer, new String[] { "replace" }, parentAppletClass).invoke(appletContainer, newApplet);
    278            }
    279            else
    280            {
    281                FMLRelaunchLog.severe("Found unknown applet parent %s, unable to inject!\n", appletContainer.getClass().getName());
    282                throw new RuntimeException();
    283            }
    284        }
    285        catch (Exception e)
    286        {
    287            throw new RuntimeException(e);
    288        }
    289        finally
    290        {
    291            if (popupWindow != null)
    292            {
    293                popupWindow.setVisible(false);
    294                popupWindow.dispose();
    295            }
    296        }
    297    }
    298
    299    public static void appletStart(Applet applet)
    300    {
    301        instance().startApplet(applet);
    302    }
    303
    304    private void startApplet(Applet applet)
    305    {
    306        if (applet.getClass().getClassLoader() == classLoader)
    307        {
    308            if (popupWindow != null)
    309            {
    310                popupWindow.setVisible(false);
    311                popupWindow.dispose();
    312            }
    313            if (RelaunchLibraryManager.downloadMonitor.shouldStopIt())
    314            {
    315                System.exit(1);
    316            }
    317            try
    318            {
    319                ReflectionHelper.findMethod(appletClass, newApplet, new String[] { "fmlStartReentry" }).invoke(newApplet);
    320            }
    321            catch (Exception e)
    322            {
    323                System.out.println("FMLRelauncher.startApplet");
    324                e.printStackTrace();
    325                throw new RuntimeException(e);
    326            }
    327        }
    328        return;
    329    }
    330
    331    public static String side()
    332    {
    333        return side;
    334    }
    335}