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}