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