001 package cpw.mods.fml.relauncher; 002 003 import java.io.File; 004 import java.io.FileInputStream; 005 import java.io.FileOutputStream; 006 import java.io.FilenameFilter; 007 import java.io.IOException; 008 import java.io.InputStream; 009 import java.io.InterruptedIOException; 010 import java.lang.reflect.Method; 011 import java.net.MalformedURLException; 012 import java.net.URL; 013 import java.net.URLConnection; 014 import java.nio.ByteBuffer; 015 import java.nio.MappedByteBuffer; 016 import java.nio.channels.FileChannel; 017 import java.nio.channels.FileChannel.MapMode; 018 import java.security.MessageDigest; 019 import java.util.ArrayList; 020 import java.util.Arrays; 021 import java.util.HashMap; 022 import java.util.List; 023 import java.util.Map; 024 import java.util.jar.Attributes; 025 import java.util.jar.JarFile; 026 import java.util.logging.Level; 027 028 import cpw.mods.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions; 029 030 public class RelaunchLibraryManager 031 { 032 private static String[] rootPlugins = { "cpw.mods.fml.relauncher.FMLCorePlugin" , "net.minecraftforge.classloading.FMLForgePlugin" }; 033 private static List<String> loadedLibraries = new ArrayList<String>(); 034 private static Map<IFMLLoadingPlugin, File> pluginLocations; 035 private static List<IFMLLoadingPlugin> loadPlugins; 036 private static List<ILibrarySet> libraries; 037 public static void handleLaunch(File mcDir, RelaunchClassLoader actualClassLoader) 038 { 039 pluginLocations = new HashMap<IFMLLoadingPlugin, File>(); 040 loadPlugins = new ArrayList<IFMLLoadingPlugin>(); 041 libraries = new ArrayList<ILibrarySet>(); 042 for (String s : rootPlugins) 043 { 044 try 045 { 046 IFMLLoadingPlugin plugin = (IFMLLoadingPlugin) Class.forName(s, true, actualClassLoader).newInstance(); 047 loadPlugins.add(plugin); 048 for (String libName : plugin.getLibraryRequestClass()) 049 { 050 libraries.add((ILibrarySet) Class.forName(libName, true, actualClassLoader).newInstance()); 051 } 052 } 053 catch (Exception e) 054 { 055 // HMMM 056 } 057 } 058 059 if (loadPlugins.isEmpty()) 060 { 061 throw new RuntimeException("A fatal error has occured - no valid fml load plugin was found - this is a completely corrupt FML installation."); 062 } 063 064 downloadMonitor.updateProgressString("All core mods are successfully located"); 065 // Now that we have the root plugins loaded - lets see what else might be around 066 String commandLineCoremods = System.getProperty("fml.coreMods.load",""); 067 for (String s : commandLineCoremods.split(",")) 068 { 069 if (s.isEmpty()) 070 { 071 continue; 072 } 073 FMLRelaunchLog.info("Found a command line coremod : %s", s); 074 try 075 { 076 actualClassLoader.addTransformerExclusion(s); 077 Class<?> coreModClass = Class.forName(s, true, actualClassLoader); 078 TransformerExclusions trExclusions = coreModClass.getAnnotation(IFMLLoadingPlugin.TransformerExclusions.class); 079 if (trExclusions!=null) 080 { 081 for (String st : trExclusions.value()) 082 { 083 actualClassLoader.addTransformerExclusion(st); 084 } 085 } 086 IFMLLoadingPlugin plugin = (IFMLLoadingPlugin) coreModClass.newInstance(); 087 loadPlugins.add(plugin); 088 if (plugin.getLibraryRequestClass()!=null) 089 { 090 for (String libName : plugin.getLibraryRequestClass()) 091 { 092 libraries.add((ILibrarySet) Class.forName(libName, true, actualClassLoader).newInstance()); 093 } 094 } 095 } 096 catch (Throwable e) 097 { 098 FMLRelaunchLog.log(Level.SEVERE,e,"Exception occured trying to load coremod %s",s); 099 throw new RuntimeException(e); 100 } 101 } 102 discoverCoreMods(mcDir, actualClassLoader, loadPlugins, libraries); 103 104 List<Throwable> caughtErrors = new ArrayList<Throwable>(); 105 try 106 { 107 File libDir; 108 try 109 { 110 libDir = setupLibDir(mcDir); 111 } 112 catch (Exception e) 113 { 114 caughtErrors.add(e); 115 return; 116 } 117 118 for (ILibrarySet lib : libraries) 119 { 120 for (int i=0; i<lib.getLibraries().length; i++) 121 { 122 boolean download = false; 123 String libName = lib.getLibraries()[i]; 124 String targFileName = libName.lastIndexOf('/')>=0 ? libName.substring(libName.lastIndexOf('/')) : libName; 125 String checksum = lib.getHashes()[i]; 126 File libFile = new File(libDir, targFileName); 127 if (!libFile.exists()) 128 { 129 try 130 { 131 downloadFile(libFile, lib.getRootURL(), libName, checksum); 132 download = true; 133 } 134 catch (Throwable e) 135 { 136 caughtErrors.add(e); 137 continue; 138 } 139 } 140 141 if (libFile.exists() && !libFile.isFile()) 142 { 143 caughtErrors.add(new RuntimeException(String.format("Found a file %s that is not a normal file - you should clear this out of the way", libName))); 144 continue; 145 } 146 147 if (!download) 148 { 149 try 150 { 151 FileInputStream fis = new FileInputStream(libFile); 152 FileChannel chan = fis.getChannel(); 153 MappedByteBuffer mappedFile = chan.map(MapMode.READ_ONLY, 0, libFile.length()); 154 String fileChecksum = generateChecksum(mappedFile); 155 fis.close(); 156 // bad checksum and I did not download this file 157 if (!checksum.equals(fileChecksum)) 158 { 159 caughtErrors.add(new RuntimeException(String.format("The file %s was found in your lib directory and has an invalid checksum %s (expecting %s) - it is unlikely to be the correct download, please move it out of the way and try again.", libName, fileChecksum, checksum))); 160 continue; 161 } 162 } 163 catch (Exception e) 164 { 165 FMLRelaunchLog.log(Level.SEVERE, e, "The library file %s could not be validated", libFile.getName()); 166 caughtErrors.add(new RuntimeException(String.format("The library file %s could not be validated", libFile.getName()),e)); 167 continue; 168 } 169 } 170 171 if (!download) 172 { 173 downloadMonitor.updateProgressString("Found library file %s present and correct in lib dir\n", libName); 174 } 175 else 176 { 177 downloadMonitor.updateProgressString("Library file %s was downloaded and verified successfully\n", libName); 178 } 179 180 try 181 { 182 actualClassLoader.addURL(libFile.toURI().toURL()); 183 loadedLibraries.add(libName); 184 } 185 catch (MalformedURLException e) 186 { 187 caughtErrors.add(new RuntimeException(String.format("Should never happen - %s is broken - probably a somehow corrupted download. Delete it and try again.", libFile.getName()), e)); 188 } 189 } 190 } 191 } 192 finally 193 { 194 if (downloadMonitor.shouldStopIt()) 195 { 196 return; 197 } 198 if (!caughtErrors.isEmpty()) 199 { 200 FMLRelaunchLog.severe("There were errors during initial FML setup. " + 201 "Some files failed to download or were otherwise corrupted. " + 202 "You will need to manually obtain the following files from " + 203 "these download links and ensure your lib directory is clean. "); 204 for (ILibrarySet set : libraries) 205 { 206 for (String file : set.getLibraries()) 207 { 208 FMLRelaunchLog.severe("*** Download "+set.getRootURL(), file); 209 } 210 } 211 FMLRelaunchLog.severe("<===========>"); 212 FMLRelaunchLog.severe("The following is the errors that caused the setup to fail. " + 213 "They may help you diagnose and resolve the issue"); 214 for (Throwable t : caughtErrors) 215 { 216 if (t.getMessage()!=null) 217 { 218 FMLRelaunchLog.severe(t.getMessage()); 219 } 220 } 221 FMLRelaunchLog.severe("<<< ==== >>>"); 222 FMLRelaunchLog.severe("The following is diagnostic information for developers to review."); 223 for (Throwable t : caughtErrors) 224 { 225 FMLRelaunchLog.log(Level.SEVERE, t, "Error details"); 226 } 227 throw new RuntimeException("A fatal error occured and FML cannot continue"); 228 } 229 } 230 231 for (IFMLLoadingPlugin plug : loadPlugins) 232 { 233 if (plug.getASMTransformerClass()!=null) 234 { 235 for (String xformClass : plug.getASMTransformerClass()) 236 { 237 actualClassLoader.registerTransformer(xformClass); 238 } 239 } 240 } 241 242 downloadMonitor.updateProgressString("Running coremod plugins"); 243 Map<String,Object> data = new HashMap<String,Object>(); 244 data.put("mcLocation", mcDir); 245 data.put("coremodList", loadPlugins); 246 for (IFMLLoadingPlugin plugin : loadPlugins) 247 { 248 downloadMonitor.updateProgressString("Running coremod plugin %s", plugin.getClass().getSimpleName()); 249 data.put("coremodLocation", pluginLocations.get(plugin)); 250 plugin.injectData(data); 251 String setupClass = plugin.getSetupClass(); 252 if (setupClass != null) 253 { 254 try 255 { 256 IFMLCallHook call = (IFMLCallHook) Class.forName(setupClass, true, actualClassLoader).newInstance(); 257 Map<String,Object> callData = new HashMap<String, Object>(); 258 callData.put("classLoader", actualClassLoader); 259 call.injectData(callData); 260 call.call(); 261 } 262 catch (Exception e) 263 { 264 throw new RuntimeException(e); 265 } 266 } 267 downloadMonitor.updateProgressString("Coremod plugin %s run successfully", plugin.getClass().getSimpleName()); 268 269 String modContainer = plugin.getModContainerClass(); 270 if (modContainer != null) 271 { 272 FMLInjectionData.containers.add(modContainer); 273 } 274 } 275 try 276 { 277 downloadMonitor.updateProgressString("Validating minecraft"); 278 Class<?> loaderClazz = Class.forName("cpw.mods.fml.common.Loader", true, actualClassLoader); 279 Method m = loaderClazz.getMethod("injectData", Object[].class); 280 m.invoke(null, (Object)FMLInjectionData.data()); 281 m = loaderClazz.getMethod("instance"); 282 m.invoke(null); 283 downloadMonitor.updateProgressString("Minecraft validated, launching..."); 284 downloadBuffer = null; 285 } 286 catch (Exception e) 287 { 288 // Load in the Loader, make sure he's ready to roll - this will initialize most of the rest of minecraft here 289 System.out.println("A CRITICAL PROBLEM OCCURED INITIALIZING MINECRAFT - LIKELY YOU HAVE AN INCORRECT VERSION FOR THIS FML"); 290 throw new RuntimeException(e); 291 } 292 } 293 294 private static void discoverCoreMods(File mcDir, RelaunchClassLoader classLoader, List<IFMLLoadingPlugin> loadPlugins, List<ILibrarySet> libraries) 295 { 296 downloadMonitor.updateProgressString("Discovering coremods"); 297 File coreMods = setupCoreModDir(mcDir); 298 FilenameFilter ff = new FilenameFilter() 299 { 300 @Override 301 public boolean accept(File dir, String name) 302 { 303 return name.endsWith(".jar"); 304 } 305 }; 306 File[] coreModList = coreMods.listFiles(ff); 307 Arrays.sort(coreModList); 308 309 for (File coreMod : coreModList) 310 { 311 downloadMonitor.updateProgressString("Found a candidate coremod %s", coreMod.getName()); 312 JarFile jar; 313 Attributes mfAttributes; 314 try 315 { 316 jar = new JarFile(coreMod); 317 mfAttributes = jar.getManifest().getMainAttributes(); 318 } 319 catch (IOException ioe) 320 { 321 FMLRelaunchLog.log(Level.SEVERE, ioe, "Unable to read the coremod jar file %s - ignoring", coreMod.getName()); 322 continue; 323 } 324 325 String fmlCorePlugin = mfAttributes.getValue("FMLCorePlugin"); 326 if (fmlCorePlugin == null) 327 { 328 FMLRelaunchLog.severe("The coremod %s does not contain a valid jar manifest- it will be ignored", coreMod.getName()); 329 continue; 330 } 331 332 // String className = fmlCorePlugin.replace('.', '/').concat(".class"); 333 // JarEntry ent = jar.getJarEntry(className); 334 // if (ent ==null) 335 // { 336 // FMLLog.severe("The coremod %s specified %s as it's loading class but it does not include it - it will be ignored", coreMod.getName(), fmlCorePlugin); 337 // continue; 338 // } 339 // try 340 // { 341 // Class<?> coreModClass = Class.forName(fmlCorePlugin, false, classLoader); 342 // FMLLog.severe("The coremods %s specified a class %s that is already present in the classpath - it will be ignored", coreMod.getName(), fmlCorePlugin); 343 // continue; 344 // } 345 // catch (ClassNotFoundException cnfe) 346 // { 347 // // didn't find it, good 348 // } 349 try 350 { 351 classLoader.addURL(coreMod.toURI().toURL()); 352 } 353 catch (MalformedURLException e) 354 { 355 FMLRelaunchLog.log(Level.SEVERE, e, "Unable to convert file into a URL. weird"); 356 continue; 357 } 358 try 359 { 360 downloadMonitor.updateProgressString("Loading coremod %s", coreMod.getName()); 361 classLoader.addTransformerExclusion(fmlCorePlugin); 362 Class<?> coreModClass = Class.forName(fmlCorePlugin, true, classLoader); 363 TransformerExclusions trExclusions = coreModClass.getAnnotation(IFMLLoadingPlugin.TransformerExclusions.class); 364 if (trExclusions!=null) 365 { 366 for (String st : trExclusions.value()) 367 { 368 classLoader.addTransformerExclusion(st); 369 } 370 } 371 IFMLLoadingPlugin plugin = (IFMLLoadingPlugin) coreModClass.newInstance(); 372 loadPlugins.add(plugin); 373 pluginLocations .put(plugin, coreMod); 374 if (plugin.getLibraryRequestClass()!=null) 375 { 376 for (String libName : plugin.getLibraryRequestClass()) 377 { 378 libraries.add((ILibrarySet) Class.forName(libName, true, classLoader).newInstance()); 379 } 380 } 381 downloadMonitor.updateProgressString("Loaded coremod %s", coreMod.getName()); 382 } 383 catch (ClassNotFoundException cnfe) 384 { 385 FMLRelaunchLog.log(Level.SEVERE, cnfe, "Coremod %s: Unable to class load the plugin %s", coreMod.getName(), fmlCorePlugin); 386 } 387 catch (ClassCastException cce) 388 { 389 FMLRelaunchLog.log(Level.SEVERE, cce, "Coremod %s: The plugin %s is not an implementor of IFMLLoadingPlugin", coreMod.getName(), fmlCorePlugin); 390 } 391 catch (InstantiationException ie) 392 { 393 FMLRelaunchLog.log(Level.SEVERE, ie, "Coremod %s: The plugin class %s was not instantiable", coreMod.getName(), fmlCorePlugin); 394 } 395 catch (IllegalAccessException iae) 396 { 397 FMLRelaunchLog.log(Level.SEVERE, iae, "Coremod %s: The plugin class %s was not accessible", coreMod.getName(), fmlCorePlugin); 398 } 399 } 400 } 401 402 /** 403 * @param mcDir 404 * @return 405 */ 406 private static File setupLibDir(File mcDir) 407 { 408 File libDir = new File(mcDir,"lib"); 409 try 410 { 411 libDir = libDir.getCanonicalFile(); 412 } 413 catch (IOException e) 414 { 415 throw new RuntimeException(String.format("Unable to canonicalize the lib dir at %s", mcDir.getName()),e); 416 } 417 if (!libDir.exists()) 418 { 419 libDir.mkdir(); 420 } 421 else if (libDir.exists() && !libDir.isDirectory()) 422 { 423 throw new RuntimeException(String.format("Found a lib file in %s that's not a directory", mcDir.getName())); 424 } 425 return libDir; 426 } 427 428 /** 429 * @param mcDir 430 * @return 431 */ 432 private static File setupCoreModDir(File mcDir) 433 { 434 File coreModDir = new File(mcDir,"coremods"); 435 try 436 { 437 coreModDir = coreModDir.getCanonicalFile(); 438 } 439 catch (IOException e) 440 { 441 throw new RuntimeException(String.format("Unable to canonicalize the coremod dir at %s", mcDir.getName()),e); 442 } 443 if (!coreModDir.exists()) 444 { 445 coreModDir.mkdir(); 446 } 447 else if (coreModDir.exists() && !coreModDir.isDirectory()) 448 { 449 throw new RuntimeException(String.format("Found a coremod file in %s that's not a directory", mcDir.getName())); 450 } 451 return coreModDir; 452 } 453 454 private static void downloadFile(File libFile, String rootUrl,String realFilePath, String hash) 455 { 456 try 457 { 458 //rootUrl.replace("%s", libFile.getPath()) 459 URL libDownload = new URL(String.format(rootUrl,realFilePath)); 460 String infoString = String.format("Downloading file %s", libDownload.toString()); 461 downloadMonitor.updateProgressString(infoString); 462 FMLRelaunchLog.info(infoString); 463 URLConnection connection = libDownload.openConnection(); 464 connection.setConnectTimeout(5000); 465 connection.setReadTimeout(5000); 466 connection.setRequestProperty("User-Agent", "FML Relaunch Downloader"); 467 int sizeGuess = connection.getContentLength(); 468 performDownload(connection.getInputStream(), sizeGuess, hash, libFile); 469 downloadMonitor.updateProgressString("Download complete"); 470 FMLRelaunchLog.info("Download complete"); 471 } 472 catch (Exception e) 473 { 474 if (downloadMonitor.shouldStopIt()) 475 { 476 FMLRelaunchLog.warning("You have stopped the downloading operation before it could complete"); 477 return; 478 } 479 if (e instanceof RuntimeException) throw (RuntimeException)e; 480 FMLRelaunchLog.severe("There was a problem downloading the file %s automatically. Perhaps you " + 481 "have an environment without internet access. You will need to download " + 482 "the file manually or restart and let it try again\n", libFile.getName()); 483 libFile.delete(); 484 throw new RuntimeException("A download error occured", e); 485 } 486 } 487 488 public static List<String> getLibraries() 489 { 490 return loadedLibraries; 491 } 492 493 private static final String HEXES = "0123456789abcdef"; 494 private static ByteBuffer downloadBuffer = ByteBuffer.allocateDirect(1 << 22); 495 static IDownloadDisplay downloadMonitor; 496 497 private static void performDownload(InputStream is, int sizeGuess, String validationHash, File target) 498 { 499 if (sizeGuess > downloadBuffer.capacity()) 500 { 501 throw new RuntimeException(String.format("The file %s is too large to be downloaded by FML - the coremod is invalid", target.getName())); 502 } 503 downloadBuffer.clear(); 504 505 int bytesRead, fullLength = 0; 506 507 downloadMonitor.resetProgress(sizeGuess); 508 try 509 { 510 downloadMonitor.setPokeThread(Thread.currentThread()); 511 byte[] smallBuffer = new byte[1024]; 512 while ((bytesRead = is.read(smallBuffer)) >= 0) { 513 downloadBuffer.put(smallBuffer, 0, bytesRead); 514 fullLength += bytesRead; 515 if (downloadMonitor.shouldStopIt()) 516 { 517 break; 518 } 519 downloadMonitor.updateProgress(fullLength); 520 } 521 is.close(); 522 downloadMonitor.setPokeThread(null); 523 downloadBuffer.limit(fullLength); 524 downloadBuffer.position(0); 525 } 526 catch (InterruptedIOException e) 527 { 528 // We were interrupted by the stop button. We're stopping now.. clear interruption flag. 529 Thread.interrupted(); 530 return; 531 } 532 catch (IOException e) 533 { 534 throw new RuntimeException(e); 535 } 536 537 538 try 539 { 540 String cksum = generateChecksum(downloadBuffer); 541 if (cksum.equals(validationHash)) 542 { 543 downloadBuffer.position(0); 544 FileOutputStream fos = new FileOutputStream(target); 545 fos.getChannel().write(downloadBuffer); 546 fos.close(); 547 } 548 else 549 { 550 throw new RuntimeException(String.format("The downloaded file %s has an invalid checksum %s (expecting %s). The download did not succeed correctly and the file has been deleted. Please try launching again.", target.getName(), cksum, validationHash)); 551 } 552 } 553 catch (Exception e) 554 { 555 if (e instanceof RuntimeException) throw (RuntimeException)e; 556 throw new RuntimeException(e); 557 } 558 559 560 561 } 562 563 private static String generateChecksum(ByteBuffer buffer) 564 { 565 try 566 { 567 MessageDigest digest = MessageDigest.getInstance("SHA-1"); 568 digest.update(buffer); 569 byte[] chksum = digest.digest(); 570 final StringBuilder hex = new StringBuilder( 2 * chksum.length ); 571 for ( final byte b : chksum ) { 572 hex.append(HEXES.charAt((b & 0xF0) >> 4)) 573 .append(HEXES.charAt((b & 0x0F))); 574 } 575 return hex.toString(); 576 } 577 catch (Exception e) 578 { 579 return null; 580 } 581 } 582 }