    001    /*
    002     * The FML Forge Mod Loader suite.
    003     * Copyright (C) 2012 cpw
    004     *
    005     * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
    006     * Software Foundation; either version 2.1 of the License, or any later version.
    007     *
    008     * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
    009     * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
    010     *
    011     * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51
    012     * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
    013     */
    014    package cpw.mods.fml.common.modloader;
    015
    016    import java.io.File;
    017    import java.io.FileReader;
    018    import java.io.FileWriter;
    019    import java.io.IOException;
    020    import java.lang.reflect.Constructor;
    021    import java.lang.reflect.Field;
    022    import java.lang.reflect.Modifier;
    023    import java.security.cert.Certificate;
    024    import java.util.ArrayList;
    025    import java.util.EnumSet;
    026    import java.util.List;
    027    import java.util.Map;
    028    import java.util.Properties;
    029    import java.util.Set;
    030    import java.util.logging.Level;
    031
    032    import net.minecraft.command.ICommand;
    033
    034    import com.google.common.base.Strings;
    035    import com.google.common.base.Throwables;
    036    import com.google.common.collect.ImmutableMap;
    037    import com.google.common.collect.Lists;
    038    import com.google.common.collect.Sets;
    039    import com.google.common.eventbus.EventBus;
    040    import com.google.common.eventbus.Subscribe;
    041
    042    import cpw.mods.fml.common.FMLCommonHandler;
    043    import cpw.mods.fml.common.FMLLog;
    044    import cpw.mods.fml.common.LoadController;
    045    import cpw.mods.fml.common.Loader;
    046    import cpw.mods.fml.common.LoaderException;
    047    import cpw.mods.fml.common.MetadataCollection;
    048    import cpw.mods.fml.common.ModClassLoader;
    049    import cpw.mods.fml.common.ModContainer;
    050    import cpw.mods.fml.common.ModMetadata;
    051    import cpw.mods.fml.common.ProxyInjector;
    052    import cpw.mods.fml.common.TickType;
    053    import cpw.mods.fml.common.discovery.ASMDataTable;
    054    import cpw.mods.fml.common.discovery.ASMDataTable.ASMData;
    055    import cpw.mods.fml.common.discovery.ContainerType;
    056    import cpw.mods.fml.common.event.FMLConstructionEvent;
    057    import cpw.mods.fml.common.event.FMLInitializationEvent;
    058    import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
    059    import cpw.mods.fml.common.event.FMLPostInitializationEvent;
    060    import cpw.mods.fml.common.event.FMLPreInitializationEvent;
    061    import cpw.mods.fml.common.event.FMLServerStartingEvent;
    062    import cpw.mods.fml.common.network.FMLNetworkHandler;
    063    import cpw.mods.fml.common.network.NetworkRegistry;
    064    import cpw.mods.fml.common.registry.GameRegistry;
    065    import cpw.mods.fml.common.registry.TickRegistry;
    066    import cpw.mods.fml.common.versioning.ArtifactVersion;
    067    import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
    068    import cpw.mods.fml.common.versioning.VersionRange;
    069    import cpw.mods.fml.relauncher.Side;
    070
    071    public class ModLoaderModContainer implements ModContainer
    072    {
    073        public BaseModProxy mod;
    074        private File modSource;
    075        public Set<ArtifactVersion> requirements = Sets.newHashSet();
    076        public ArrayList<ArtifactVersion> dependencies = Lists.newArrayList();
    077        public ArrayList<ArtifactVersion> dependants = Lists.newArrayList();
    078        private ContainerType sourceType;
    079        private ModMetadata metadata;
    080        private ProxyInjector sidedProxy;
    081        private BaseModTicker gameTickHandler;
    082        private BaseModTicker guiTickHandler;
    083        private String modClazzName;
    084        private String modId;
    085        private EventBus bus;
    086        private LoadController controller;
    087        private boolean enabled = true;
    088        private String sortingProperties;
    089        private ArtifactVersion processedVersion;
    090        private boolean isNetworkMod;
    091        private List<ICommand> serverCommands = Lists.newArrayList();
    092
    093        public ModLoaderModContainer(String className, File modSource, String sortingProperties)
    094        {
    095            this.modClazzName = className;
    096            this.modSource = modSource;
    097            this.modId = className.contains(".") ? className.substring(className.lastIndexOf('.')+1) : className;
    098            this.sortingProperties = Strings.isNullOrEmpty(sortingProperties) ? "" : sortingProperties;
    099        }
    100
    101        /**
    102         * We only instantiate this for "not mod mods"
    103         * @param instance
    104         */
    105        ModLoaderModContainer(BaseModProxy instance) {
    106            this.mod=instance;
    107            this.gameTickHandler = new BaseModTicker(instance, false);
    108            this.guiTickHandler = new BaseModTicker(instance, true);
    109        }
    110
    111        /**
    112         *
    113         */
    114        private void configureMod(Class<? extends BaseModProxy> modClazz, ASMDataTable asmData)
    115        {
    116            File configDir = Loader.instance().getConfigDir();
    117            File modConfig = new File(configDir, String.format("%s.cfg", getModId()));
    118            Properties props = new Properties();
    119
    120            boolean existingConfigFound = false;
    121            boolean mlPropFound = false;
    122
    123            if (modConfig.exists())
    124            {
    125                try
    126                {
    127                    FMLLog.fine("Reading existing configuration file for %s : %s", getModId(), modConfig.getName());
    128                    FileReader configReader = new FileReader(modConfig);
    129                    props.load(configReader);
    130                    configReader.close();
    131                }
    132                catch (Exception e)
    133                {
    134                    FMLLog.log(Level.SEVERE, e, "Error occured reading mod configuration file %s", modConfig.getName());
    135                    throw new LoaderException(e);
    136                }
    137                existingConfigFound = true;
    138            }
    139
    140            StringBuffer comments = new StringBuffer();
    141            comments.append("MLProperties: name (type:default) min:max -- information\n");
    142
    143
    144            List<ModProperty> mlPropFields = Lists.newArrayList();
    145            try
    146            {
    147                for (ASMData dat : Sets.union(asmData.getAnnotationsFor(this).get("net.minecraft.src.MLProp"), asmData.getAnnotationsFor(this).get("MLProp")))
    148                {
    149                    if (dat.getClassName().equals(modClazzName))
    150                    {
    151                        try
    152                        {
    153                            mlPropFields.add(new ModProperty(modClazz.getDeclaredField(dat.getObjectName()), dat.getAnnotationInfo()));
    154                            FMLLog.finest("Found an MLProp field %s in %s", dat.getObjectName(), getModId());
    155                        }
    156                        catch (Exception e)
    157                        {
    158                            FMLLog.log(Level.WARNING, e, "An error occured trying to access field %s in mod %s", dat.getObjectName(), getModId());
    159                        }
    160                    }
    161                }
    162                for (ModProperty property : mlPropFields)
    163                {
    164                    if (!Modifier.isStatic(property.field().getModifiers()))
    165                    {
    166                        FMLLog.info("The MLProp field %s in mod %s appears not to be static", property.field().getName(), getModId());
    167                        continue;
    168                    }
    169                    FMLLog.finest("Considering MLProp field %s", property.field().getName());
    170                    Field f = property.field();
    171                    String propertyName = !Strings.nullToEmpty(property.name()).isEmpty() ? property.name() : f.getName();
    172                    String propertyValue = null;
    173                    Object defaultValue = null;
    174
    175                    try
    176                    {
    177                        defaultValue = f.get(null);
    178                        propertyValue = props.getProperty(propertyName, extractValue(defaultValue));
    179                        Object currentValue = parseValue(propertyValue, property, f.getType(), propertyName);
    180                        FMLLog.finest("Configuration for %s.%s found values default: %s, configured: %s, interpreted: %s", modClazzName, propertyName, defaultValue, propertyValue, currentValue);
    181
    182                        if (currentValue != null && !currentValue.equals(defaultValue))
    183                        {
    184                            FMLLog.finest("Configuration for %s.%s value set to: %s", modClazzName, propertyName, currentValue);
    185                            f.set(null, currentValue);
    186                        }
    187                    }
    188                    catch (Exception e)
    189                    {
    190                        FMLLog.log(Level.SEVERE, e, "Invalid configuration found for %s in %s", propertyName, modConfig.getName());
    191                        throw new LoaderException(e);
    192                    }
    193                    finally
    194                    {
    195                        comments.append(String.format("MLProp : %s (%s:%s", propertyName, f.getType().getName(), defaultValue));
    196
    197                        if (property.min() != Double.MIN_VALUE)
    198                        {
    199                            comments.append(",>=").append(String.format("%.1f", property.min()));
    200                        }
    201
    202                        if (property.max() != Double.MAX_VALUE)
    203                        {
    204                            comments.append(",<=").append(String.format("%.1f", property.max()));
    205                        }
    206
    207                        comments.append(")");
    208
    209                        if (!Strings.nullToEmpty(property.info()).isEmpty())
    210                        {
    211                            comments.append(" -- ").append(property.info());
    212                        }
    213
    214                        if (propertyValue != null)
    215                        {
    216                            props.setProperty(propertyName, extractValue(propertyValue));
    217                        }
    218                        comments.append("\n");
    219                    }
    220                    mlPropFound = true;
    221                }
    222            }
    223            finally
    224            {
    225                if (!mlPropFound && !existingConfigFound)
    226                {
    227                    FMLLog.fine("No MLProp configuration for %s found or required. No file written", getModId());
    228                    return;
    229                }
    230
    231                if (!mlPropFound && existingConfigFound)
    232                {
    233                    File mlPropBackup = new File(modConfig.getParent(),modConfig.getName()+".bak");
    234                    FMLLog.fine("MLProp configuration file for %s found but not required. Attempting to rename file to %s", getModId(), mlPropBackup.getName());
    235                    boolean renamed = modConfig.renameTo(mlPropBackup);
    236                    if (renamed)
    237                    {
    238                        FMLLog.fine("Unused MLProp configuration file for %s renamed successfully to %s", getModId(), mlPropBackup.getName());
    239                    }
    240                    else
    241                    {
    242                        FMLLog.fine("Unused MLProp configuration file for %s renamed UNSUCCESSFULLY to %s", getModId(), mlPropBackup.getName());
    243                    }
    244
    245                    return;
    246                }
    247                try
    248                {
    249                    FileWriter configWriter = new FileWriter(modConfig);
    250                    props.store(configWriter, comments.toString());
    251                    configWriter.close();
    252                    FMLLog.fine("Configuration for %s written to %s", getModId(), modConfig.getName());
    253                }
    254                catch (IOException e)
    255                {
    256                    FMLLog.log(Level.SEVERE, e, "Error trying to write the config file %s", modConfig.getName());
    257                    throw new LoaderException(e);
    258                }
    259            }
    260        }
    261
    262        private Object parseValue(String val, ModProperty property, Class<?> type, String propertyName)
    263        {
    264            if (type.isAssignableFrom(String.class))
    265            {
    266                return (String)val;
    267            }
    268            else if (type.isAssignableFrom(Boolean.TYPE) || type.isAssignableFrom(Boolean.class))
    269            {
    270                return Boolean.parseBoolean(val);
    271            }
    272            else if (Number.class.isAssignableFrom(type) || type.isPrimitive())
    273            {
    274                Number n = null;
    275
    276                if (type.isAssignableFrom(Double.TYPE) || Double.class.isAssignableFrom(type))
    277                {
    278                    n = Double.parseDouble(val);
    279                }
    280                else if (type.isAssignableFrom(Float.TYPE) || Float.class.isAssignableFrom(type))
    281                {
    282                    n = Float.parseFloat(val);
    283                }
    284                else if (type.isAssignableFrom(Long.TYPE) || Long.class.isAssignableFrom(type))
    285                {
    286                    n = Long.parseLong(val);
    287                }
    288                else if (type.isAssignableFrom(Integer.TYPE) || Integer.class.isAssignableFrom(type))
    289                {
    290                    n = Integer.parseInt(val);
    291                }
    292                else if (type.isAssignableFrom(Short.TYPE) || Short.class.isAssignableFrom(type))
    293                {
    294                    n = Short.parseShort(val);
    295                }
    296                else if (type.isAssignableFrom(Byte.TYPE) || Byte.class.isAssignableFrom(type))
    297                {
    298                    n = Byte.parseByte(val);
    299                }
    300                else
    301                {
    302                    throw new IllegalArgumentException(String.format("MLProp declared on %s of type %s, an unsupported type",propertyName, type.getName()));
    303                }
    304
    305                double dVal = n.doubleValue();
    306                if ((property.min()!=Double.MIN_VALUE && dVal < property.min()) || (property.max()!=Double.MAX_VALUE && dVal > property.max()))
    307                {
    308                    FMLLog.warning("Configuration for %s.%s found value %s outside acceptable range %s,%s", modClazzName,propertyName, n, property.min(), property.max());
    309                    return null;
    310                }
    311                else
    312                {
    313                    return n;
    314                }
    315            }
    316
    317            throw new IllegalArgumentException(String.format("MLProp declared on %s of type %s, an unsupported type",propertyName, type.getName()));
    318        }
    319        private String extractValue(Object value)
    320        {
    321            if (String.class.isInstance(value))
    322            {
    323                return (String)value;
    324            }
    325            else if (Number.class.isInstance(value) || Boolean.class.isInstance(value))
    326            {
    327                return String.valueOf(value);
    328            }
    329            else
    330            {
    331                throw new IllegalArgumentException("MLProp declared on non-standard type");
    332            }
    333        }
    334
    335        @Override
    336        public String getName()
    337        {
    338            return mod != null ? mod.getName() : modId;
    339        }
    340
    341        @Deprecated
    342        public static ModContainer findContainerFor(BaseModProxy mod)
    343        {
    344            return FMLCommonHandler.instance().findContainerFor(mod);
    345        }
    346
    347        @Override
    348        public String getSortingRules()
    349        {
    350            return sortingProperties;
    351        }
    352
    353        @Override
    354        public boolean matches(Object mod)
    355        {
    356            return this.mod == mod;
    357        }
    358
    359        /**
    360         * Find all the BaseMods in the system
    361         * @param <A>
    362         */
    363        public static <A extends BaseModProxy> List<A> findAll(Class<A> clazz)
    364        {
    365            ArrayList<A> modList = new ArrayList<A>();
    366
    367            for (ModContainer mc : Loader.instance().getActiveModList())
    368            {
    369                if (mc instanceof ModLoaderModContainer && mc.getMod()!=null)
    370                {
    371                    modList.add((A)((ModLoaderModContainer)mc).mod);
    372                }
    373            }
    374
    375            return modList;
    376        }
    377
    378        @Override
    379        public File getSource()
    380        {
    381            return modSource;
    382        }
    383
    384        @Override
    385        public Object getMod()
    386        {
    387            return mod;
    388        }
    389
    390        @Override
    391        public Set<ArtifactVersion> getRequirements()
    392        {
    393            return requirements;
    394        }
    395
    396        @Override
    397        public List<ArtifactVersion> getDependants()
    398        {
    399            return dependants;
    400        }
    401
    402        @Override
    403        public List<ArtifactVersion> getDependencies()
    404        {
    405            return dependencies;
    406        }
    407
    408
    409        public String toString()
    410        {
    411            return modId;
    412        }
    413
    414        @Override
    415        public ModMetadata getMetadata()
    416        {
    417            return metadata;
    418        }
    419
    420        @Override
    421        public String getVersion()
    422        {
    423            if (mod == null || mod.getVersion() == null)
    424            {
    425                return "Not available";
    426            }
    427            return mod.getVersion();
    428        }
    429
    430        public BaseModTicker getGameTickHandler()
    431        {
    432            return this.gameTickHandler;
    433        }
    434
    435        public BaseModTicker getGUITickHandler()
    436        {
    437            return this.guiTickHandler;
    438        }
    439
    440        @Override
    441        public String getModId()
    442        {
    443            return modId;
    444        }
    445
    446        @Override
    447        public void bindMetadata(MetadataCollection mc)
    448        {
    449            Map<String, Object> dummyMetadata = ImmutableMap.<String,Object>builder().put("name", modId).put("version", "1.0").build();
    450            this.metadata = mc.getMetadataForId(modId, dummyMetadata);
    451            Loader.instance().computeDependencies(sortingProperties, getRequirements(), getDependencies(), getDependants());
    452        }
    453
    454        @Override
    455        public void setEnabledState(boolean enabled)
    456        {
    457            this.enabled = enabled;
    458        }
    459
    460        @Override
    461        public boolean registerBus(EventBus bus, LoadController controller)
    462        {
    463            if (this.enabled)
    464            {
    465                FMLLog.fine("Enabling mod %s", getModId());
    466                this.bus = bus;
    467                this.controller = controller;
    468                bus.register(this);
    469                return true;
    470            }
    471            else
    472            {
    473                return false;
    474            }
    475        }
    476
    477        // Lifecycle mod events
    478
    479        @Subscribe
    480        public void constructMod(FMLConstructionEvent event)
    481        {
    482            try
    483            {
    484                ModClassLoader modClassLoader = event.getModClassLoader();
    485                modClassLoader.addFile(modSource);
    486                EnumSet<TickType> ticks = EnumSet.noneOf(TickType.class);
    487                this.gameTickHandler = new BaseModTicker(ticks, false);
    488                this.guiTickHandler = new BaseModTicker(ticks.clone(), true);
    489                Class<? extends BaseModProxy> modClazz = (Class<? extends BaseModProxy>) modClassLoader.loadBaseModClass(modClazzName);
    490                configureMod(modClazz, event.getASMHarvestedData());
    491                isNetworkMod = FMLNetworkHandler.instance().registerNetworkMod(this, modClazz, event.getASMHarvestedData());
    492                ModLoaderNetworkHandler dummyHandler = null;
    493                if (!isNetworkMod)
    494                {
    495                    FMLLog.fine("Injecting dummy network mod handler for BaseMod %s", getModId());
    496                    dummyHandler = new ModLoaderNetworkHandler(this);
    497                    FMLNetworkHandler.instance().registerNetworkMod(dummyHandler);
    498                }
    499                Constructor<? extends BaseModProxy> ctor = modClazz.getConstructor();
    500                ctor.setAccessible(true);
    501                mod = modClazz.newInstance();
    502                if (dummyHandler != null)
    503                {
    504                    dummyHandler.setBaseMod(mod);
    505                }
    506                ProxyInjector.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide());
    507            }
    508            catch (Exception e)
    509            {
    510                controller.errorOccurred(this, e);
    511                Throwables.propagateIfPossible(e);
    512            }
    513        }
    514
    515        @Subscribe
    516        public void preInit(FMLPreInitializationEvent event)
    517        {
    518            try
    519            {
    520                this.gameTickHandler.setMod(mod);
    521                this.guiTickHandler.setMod(mod);
    522                TickRegistry.registerTickHandler(this.gameTickHandler, Side.CLIENT);
    523                TickRegistry.registerTickHandler(this.guiTickHandler, Side.CLIENT);
    524                GameRegistry.registerWorldGenerator(ModLoaderHelper.buildWorldGenHelper(mod));
    525                GameRegistry.registerFuelHandler(ModLoaderHelper.buildFuelHelper(mod));
    526                GameRegistry.registerCraftingHandler(ModLoaderHelper.buildCraftingHelper(mod));
    527                GameRegistry.registerPickupHandler(ModLoaderHelper.buildPickupHelper(mod));
    528                GameRegistry.registerDispenserHandler(ModLoaderHelper.buildDispenseHelper(mod));
    529                NetworkRegistry.instance().registerChatListener(ModLoaderHelper.buildChatListener(mod));
    530                NetworkRegistry.instance().registerConnectionHandler(ModLoaderHelper.buildConnectionHelper(mod));
    531            }
    532            catch (Exception e)
    533            {
    534                controller.errorOccurred(this, e);
    535                Throwables.propagateIfPossible(e);
    536            }
    537        }
    538
    539
    540        @Subscribe
    541        public void init(FMLInitializationEvent event)
    542        {
    543            try
    544            {
    545                mod.load();
    546            }
    547            catch (Throwable t)
    548            {
    549                controller.errorOccurred(this, t);
    550                Throwables.propagateIfPossible(t);
    551            }
    552        }
    553
    554        @Subscribe
    555        public void postInit(FMLPostInitializationEvent event)
    556        {
    557            try
    558            {
    559                mod.modsLoaded();
    560            }
    561            catch (Throwable t)
    562            {
    563                controller.errorOccurred(this, t);
    564                Throwables.propagateIfPossible(t);
    565            }
    566        }
    567
    568        @Subscribe
    569        public void loadComplete(FMLLoadCompleteEvent complete)
    570        {
    571            ModLoaderHelper.finishModLoading(this);
    572        }
    573
    574        @Subscribe
    575        public void serverStarting(FMLServerStartingEvent evt)
    576        {
    577            for (ICommand cmd : serverCommands)
    578            {
    579                evt.registerServerCommand(cmd);
    580            }
    581        }
    582        @Override
    583        public ArtifactVersion getProcessedVersion()
    584        {
    585            if (processedVersion == null)
    586            {
    587                processedVersion = new DefaultArtifactVersion(modId, getVersion());
    588            }
    589            return processedVersion;
    590        }
    591
    592        @Override
    593        public boolean isImmutable()
    594        {
    595            return false;
    596        }
    597
    598        @Override
    599        public boolean isNetworkMod()
    600        {
    601            return this.isNetworkMod;
    602        }
    603
    604        @Override
    605        public String getDisplayVersion()
    606        {
    607            return metadata!=null ? metadata.version : getVersion();
    608        }
    609
    610        public void addServerCommand(ICommand command)
    611        {
    612            serverCommands .add(command);
    613        }
    614
    615        @Override
    616        public VersionRange acceptableMinecraftVersionRange()
    617        {
    618            return Loader.instance().getMinecraftModContainer().getStaticVersionRange();
    619        }
    620        @Override
    621        public Certificate getSigningCertificate()
    622        {
    623            return null;
    624        }
    625    }