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