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