    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 */
    014package cpw.mods.fml.common.modloader;
    015
    016import java.io.File;
    017import java.io.FileReader;
    018import java.io.FileWriter;
    019import java.io.IOException;
    020import java.lang.reflect.Constructor;
    021import java.lang.reflect.Field;
    022import java.lang.reflect.Modifier;
    023import java.security.cert.Certificate;
    024import java.util.ArrayList;
    025import java.util.EnumSet;
    026import java.util.List;
    027import java.util.Map;
    028import java.util.Properties;
    029import java.util.Set;
    030import java.util.logging.Level;
    031
    032import net.minecraft.command.ICommand;
    033
    034import com.google.common.base.Strings;
    035import com.google.common.base.Throwables;
    036import com.google.common.collect.ImmutableMap;
    037import com.google.common.collect.Lists;
    038import com.google.common.collect.Sets;
    039import com.google.common.eventbus.EventBus;
    040import com.google.common.eventbus.Subscribe;
    041
    042import cpw.mods.fml.common.FMLCommonHandler;
    043import cpw.mods.fml.common.FMLLog;
    044import cpw.mods.fml.common.LoadController;
    045import cpw.mods.fml.common.Loader;
    046import cpw.mods.fml.common.LoaderException;
    047import cpw.mods.fml.common.MetadataCollection;
    048import cpw.mods.fml.common.ModClassLoader;
    049import cpw.mods.fml.common.ModContainer;
    050import cpw.mods.fml.common.ModMetadata;
    051import cpw.mods.fml.common.ProxyInjector;
    052import cpw.mods.fml.common.TickType;
    053import cpw.mods.fml.common.discovery.ASMDataTable;
    054import cpw.mods.fml.common.discovery.ASMDataTable.ASMData;
    055import cpw.mods.fml.common.discovery.ContainerType;
    056import cpw.mods.fml.common.event.FMLConstructionEvent;
    057import cpw.mods.fml.common.event.FMLInitializationEvent;
    058import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
    059import cpw.mods.fml.common.event.FMLPostInitializationEvent;
    060import cpw.mods.fml.common.event.FMLPreInitializationEvent;
    061import cpw.mods.fml.common.event.FMLServerStartingEvent;
    062import cpw.mods.fml.common.network.FMLNetworkHandler;
    063import cpw.mods.fml.common.network.NetworkRegistry;
    064import cpw.mods.fml.common.registry.GameRegistry;
    065import cpw.mods.fml.common.registry.TickRegistry;
    066import cpw.mods.fml.common.versioning.ArtifactVersion;
    067import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
    068import cpw.mods.fml.common.versioning.VersionRange;
    069import cpw.mods.fml.relauncher.Side;
    070
    071public 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}