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    }