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                System.out.println("Downloading file: " + libDownload.getHost() + libDownload.getPath());
461                System.out.println("The libFile's path is " + libFile.getAbsolutePath());
462                String infoString = String.format("Downloading file %s", libDownload.toString());
463                downloadMonitor.updateProgressString(infoString);
464                FMLRelaunchLog.info(infoString);
465                URLConnection connection = libDownload.openConnection();
466                connection.setConnectTimeout(5000);
467                connection.setReadTimeout(5000);
468                connection.setRequestProperty("User-Agent", "FML Relaunch Downloader");
469                int sizeGuess = connection.getContentLength();
470                performDownload(connection.getInputStream(), sizeGuess, hash, libFile);
471                downloadMonitor.updateProgressString("Download complete");
472                FMLRelaunchLog.info("Download complete");
473            }
474            catch (Exception e)
475            {
476                if (downloadMonitor.shouldStopIt())
477                {
478                    FMLRelaunchLog.warning("You have stopped the downloading operation before it could complete");
479                    return;
480                }
481                if (e instanceof RuntimeException) throw (RuntimeException)e;
482                FMLRelaunchLog.severe("There was a problem downloading the file %s automatically. Perhaps you " +
483                        "have an environment without internet access. You will need to download " +
484                        "the file manually or restart and let it try again\n", libFile.getName());
485                libFile.delete();
486                throw new RuntimeException("A download error occured", e);
487            }
488        }
489    
490        public static List<String> getLibraries()
491        {
492            return loadedLibraries;
493        }
494    
495        private static final String HEXES = "0123456789abcdef";
496        private static ByteBuffer downloadBuffer = ByteBuffer.allocateDirect(1 << 22);
497        static IDownloadDisplay downloadMonitor;
498    
499        private static void performDownload(InputStream is, int sizeGuess, String validationHash, File target)
500        {
501            if (sizeGuess > downloadBuffer.capacity())
502            {
503                throw new RuntimeException(String.format("The file %s is too large to be downloaded by FML - the coremod is invalid", target.getName()));
504            }
505            downloadBuffer.clear();
506    
507            int bytesRead, fullLength = 0;
508    
509            downloadMonitor.resetProgress(sizeGuess);
510            try
511            {
512                downloadMonitor.setPokeThread(Thread.currentThread());
513                byte[] smallBuffer = new byte[1024];
514                while ((bytesRead = is.read(smallBuffer)) >= 0) {
515                    downloadBuffer.put(smallBuffer, 0, bytesRead);
516                    fullLength += bytesRead;
517                    if (downloadMonitor.shouldStopIt())
518                    {
519                        break;
520                    }
521                    downloadMonitor.updateProgress(fullLength);
522                }
523                is.close();
524                downloadMonitor.setPokeThread(null);
525                downloadBuffer.limit(fullLength);
526                downloadBuffer.position(0);
527            }
528            catch (InterruptedIOException e)
529            {
530                // We were interrupted by the stop button. We're stopping now.. clear interruption flag.
531                Thread.interrupted();
532                return;
533            }
534            catch (IOException e)
535            {
536                throw new RuntimeException(e);
537            }
538    
539    
540            try
541            {
542                String cksum = generateChecksum(downloadBuffer);
543                if (cksum.equals(validationHash))
544                {
545                    downloadBuffer.position(0);
546                    FileOutputStream fos = new FileOutputStream(target);
547                    fos.getChannel().write(downloadBuffer);
548                    fos.close();
549                }
550                else
551                {
552                    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));
553                }
554            }
555            catch (Exception e)
556            {
557                if (e instanceof RuntimeException) throw (RuntimeException)e;
558                throw new RuntimeException(e);
559            }
560    
561    
562    
563        }
564    
565        private static String generateChecksum(ByteBuffer buffer)
566        {
567            try
568            {
569                MessageDigest digest = MessageDigest.getInstance("SHA-1");
570                digest.update(buffer);
571                byte[] chksum = digest.digest();
572                final StringBuilder hex = new StringBuilder( 2 * chksum.length );
573                for ( final byte b : chksum ) {
574                  hex.append(HEXES.charAt((b & 0xF0) >> 4))
575                     .append(HEXES.charAt((b & 0x0F)));
576                }
577                return hex.toString();
578            }
579            catch (Exception e)
580            {
581                return null;
582            }
583        }
584    }