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