    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    }