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}