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
167        try
168        {
169            RelaunchLibraryManager.handleLaunch(minecraftHome, classLoader);
170        }
171        catch (Throwable t)
172        {
173            if (popupWindow != null)
174            {
175                try
176                {
177                    String logFile = new File(minecraftHome, "ForgeModLoader-client-0.log").getCanonicalPath();
178                    JOptionPane.showMessageDialog(popupWindow, String.format(
179                            "<html><div align=\"center\"><font size=\"+1\">There was a fatal error starting up minecraft and FML</font></div><br/>"
180                                    + "Minecraft cannot launch in it's current configuration<br/>"
181                                    + "Please consult the file <i><a href=\"file:///%s\">%s</a></i> for further information</html>", logFile, logFile),
182                            "Fatal FML error", JOptionPane.ERROR_MESSAGE);
183                }
184                catch (Exception ex)
185                {
186                    // ah well, we tried
187                }
188            }
189            throw new RuntimeException(t);
190        }
191    }
192
193    /**
194     * @return the location of the client home
195     */
196    private File computeExistingClientHome()
197    {
198        Class<? super Object> mcMaster = ReflectionHelper.getClass(getClass().getClassLoader(), "net.minecraft.client.Minecraft");
199        // If we get the system property we inject into the old MC, setup the
200        // dir, then pull the value
201        String str = System.getProperty("minecraft.applet.TargetDirectory");
202        if (str != null)
203        {
204            str = str.replace('/', File.separatorChar);
205            ReflectionHelper.setPrivateValue(mcMaster, null, new File(str), "minecraftDir", "an", "minecraftDir");
206        }
207        // We force minecraft to setup it's homedir very early on so we can
208        // inject stuff into it
209        Method setupHome = ReflectionHelper.findMethod(mcMaster, null, new String[] { "getMinecraftDir", "getMinecraftDir", "b" });
210        try
211        {
212            setupHome.invoke(null);
213        }
214        catch (Exception e)
215        {
216            // Hmmm
217        }
218        File minecraftHome = ReflectionHelper.getPrivateValue(mcMaster, null, "minecraftDir", "an", "minecraftDir");
219        return minecraftHome;
220    }
221
222    public static void appletEntry(Applet minecraftApplet)
223    {
224        side = "CLIENT";
225        logFileNamePattern = "ForgeModLoader-client-%g.log";
226        instance().relaunchApplet(minecraftApplet);
227    }
228
229    private void relaunchApplet(Applet minecraftApplet)
230    {
231        showWindow(true);
232
233        if (minecraftApplet.getClass().getClassLoader() == classLoader)
234        {
235            if (popupWindow != null)
236            {
237                popupWindow.setVisible(false);
238                popupWindow.dispose();
239            }
240            try
241            {
242                newApplet = minecraftApplet;
243                appletClass = ReflectionHelper.getClass(classLoader, "net.minecraft.client.MinecraftApplet");
244                ReflectionHelper.findMethod(appletClass, newApplet, new String[] { "fmlInitReentry" }).invoke(newApplet);
245                return;
246            }
247            catch (Exception e)
248            {
249                System.out.println("FMLRelauncher.relaunchApplet");
250                e.printStackTrace();
251                throw new RuntimeException(e);
252            }
253        }
254
255        File mcDir = computeExistingClientHome();
256        setupHome(mcDir);
257        setupNewClientHome(mcDir);
258
259        Class<? super Object> parentAppletClass = ReflectionHelper.getClass(getClass().getClassLoader(), "java.applet.Applet");
260
261        try
262        {
263            appletClass = ReflectionHelper.getClass(classLoader, "net.minecraft.client.MinecraftApplet");
264            newApplet = appletClass.newInstance();
265            Object appletContainer = ReflectionHelper.getPrivateValue(ReflectionHelper.getClass(getClass().getClassLoader(), "java.awt.Component"),
266                    minecraftApplet, "parent");
267
268            String launcherClassName = System.getProperty("minecraft.applet.WrapperClass", "net.minecraft.Launcher");
269            Class<? super Object> launcherClass = ReflectionHelper.getClass(getClass().getClassLoader(), launcherClassName);
270            if (launcherClass.isInstance(appletContainer))
271            {
272                ReflectionHelper.findMethod(ReflectionHelper.getClass(getClass().getClassLoader(), "java.awt.Container"), minecraftApplet,
273                        new String[] { "removeAll" }).invoke(appletContainer);
274                ReflectionHelper.findMethod(launcherClass, appletContainer, new String[] { "replace" }, parentAppletClass).invoke(appletContainer, newApplet);
275            }
276            else
277            {
278                FMLRelaunchLog.severe("Found unknown applet parent %s, unable to inject!\n", appletContainer.getClass().getName());
279                throw new RuntimeException();
280            }
281        }
282        catch (Exception e)
283        {
284            throw new RuntimeException(e);
285        }
286        finally
287        {
288            if (popupWindow != null)
289            {
290                popupWindow.setVisible(false);
291                popupWindow.dispose();
292            }
293        }
294    }
295
296    public static void appletStart(Applet applet)
297    {
298        instance().startApplet(applet);
299    }
300
301    private void startApplet(Applet applet)
302    {
303        if (applet.getClass().getClassLoader() == classLoader)
304        {
305            if (popupWindow != null)
306            {
307                popupWindow.setVisible(false);
308                popupWindow.dispose();
309            }
310            if (RelaunchLibraryManager.downloadMonitor.shouldStopIt())
311            {
312                System.exit(1);
313            }
314            try
315            {
316                ReflectionHelper.findMethod(appletClass, newApplet, new String[] { "fmlStartReentry" }).invoke(newApplet);
317            }
318            catch (Exception e)
319            {
320                System.out.println("FMLRelauncher.startApplet");
321                e.printStackTrace();
322                throw new RuntimeException(e);
323            }
324        }
325        return;
326    }
327
328    public static String side()
329    {
330        return side;
331    }
332}