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