    001/*
    002 * Forge Mod Loader
    003 * Copyright (c) 2012-2013 cpw.
    004 * All rights reserved. This program and the accompanying materials
    005 * are made available under the terms of the GNU Lesser Public License v2.1
    006 * which accompanies this distribution, and is available at
    007 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
    008 *
    009 * Contributors:
    010 *     cpw - implementation
    011 */
    012
    013package cpw.mods.fml.common.modloader;
    014
    015import java.io.File;
    016import java.io.FileReader;
    017import java.io.FileWriter;
    018import java.io.IOException;
    019import java.lang.reflect.Constructor;
    020import java.lang.reflect.Field;
    021import java.lang.reflect.Modifier;
    022import java.security.cert.Certificate;
    023import java.util.ArrayList;
    024import java.util.EnumSet;
    025import java.util.List;
    026import java.util.Map;
    027import java.util.Properties;
    028import java.util.Set;
    029import java.util.logging.Level;
    030
    031import net.minecraft.command.ICommand;
    032
    033import com.google.common.base.Strings;
    034import com.google.common.base.Throwables;
    035import com.google.common.collect.ImmutableMap;
    036import com.google.common.collect.Lists;
    037import com.google.common.collect.Sets;
    038import com.google.common.eventbus.EventBus;
    039import com.google.common.eventbus.Subscribe;
    040
    041import cpw.mods.fml.common.FMLCommonHandler;
    042import cpw.mods.fml.common.FMLLog;
    043import cpw.mods.fml.common.LoadController;
    044import cpw.mods.fml.common.Loader;
    045import cpw.mods.fml.common.LoaderException;
    046import cpw.mods.fml.common.MetadataCollection;
    047import cpw.mods.fml.common.ModClassLoader;
    048import cpw.mods.fml.common.ModContainer;
    049import cpw.mods.fml.common.ModMetadata;
    050import cpw.mods.fml.common.ProxyInjector;
    051import cpw.mods.fml.common.TickType;
    052import cpw.mods.fml.common.discovery.ASMDataTable;
    053import cpw.mods.fml.common.discovery.ASMDataTable.ASMData;
    054import cpw.mods.fml.common.discovery.ContainerType;
    055import cpw.mods.fml.common.event.FMLConstructionEvent;
    056import cpw.mods.fml.common.event.FMLInitializationEvent;
    057import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
    058import cpw.mods.fml.common.event.FMLPostInitializationEvent;
    059import cpw.mods.fml.common.event.FMLPreInitializationEvent;
    060import cpw.mods.fml.common.event.FMLServerStartingEvent;
    061import cpw.mods.fml.common.network.FMLNetworkHandler;
    062import cpw.mods.fml.common.network.NetworkRegistry;
    063import cpw.mods.fml.common.registry.GameRegistry;
    064import cpw.mods.fml.common.registry.TickRegistry;
    065import cpw.mods.fml.common.versioning.ArtifactVersion;
    066import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
    067import cpw.mods.fml.common.versioning.VersionRange;
    068import cpw.mods.fml.relauncher.Side;
    069
    070public 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    @Override
    341    public String getSortingRules()
    342    {
    343        return sortingProperties;
    344    }
    345
    346    @Override
    347    public boolean matches(Object mod)
    348    {
    349        return this.mod == mod;
    350    }
    351
    352    /**
    353     * Find all the BaseMods in the system
    354     * @param <A>
    355     */
    356    public static <A extends BaseModProxy> List<A> findAll(Class<A> clazz)
    357    {
    358        ArrayList<A> modList = new ArrayList<A>();
    359
    360        for (ModContainer mc : Loader.instance().getActiveModList())
    361        {
    362            if (mc instanceof ModLoaderModContainer && mc.getMod()!=null)
    363            {
    364                modList.add((A)((ModLoaderModContainer)mc).mod);
    365            }
    366        }
    367
    368        return modList;
    369    }
    370
    371    @Override
    372    public File getSource()
    373    {
    374        return modSource;
    375    }
    376
    377    @Override
    378    public Object getMod()
    379    {
    380        return mod;
    381    }
    382
    383    @Override
    384    public Set<ArtifactVersion> getRequirements()
    385    {
    386        return requirements;
    387    }
    388
    389    @Override
    390    public List<ArtifactVersion> getDependants()
    391    {
    392        return dependants;
    393    }
    394
    395    @Override
    396    public List<ArtifactVersion> getDependencies()
    397    {
    398        return dependencies;
    399    }
    400
    401
    402    public String toString()
    403    {
    404        return modId;
    405    }
    406
    407    @Override
    408    public ModMetadata getMetadata()
    409    {
    410        return metadata;
    411    }
    412
    413    @Override
    414    public String getVersion()
    415    {
    416        if (mod == null || mod.getVersion() == null)
    417        {
    418            return "Not available";
    419        }
    420        return mod.getVersion();
    421    }
    422
    423    public BaseModTicker getGameTickHandler()
    424    {
    425        return this.gameTickHandler;
    426    }
    427
    428    public BaseModTicker getGUITickHandler()
    429    {
    430        return this.guiTickHandler;
    431    }
    432
    433    @Override
    434    public String getModId()
    435    {
    436        return modId;
    437    }
    438
    439    @Override
    440    public void bindMetadata(MetadataCollection mc)
    441    {
    442        Map<String, Object> dummyMetadata = ImmutableMap.<String,Object>builder().put("name", modId).put("version", "1.0").build();
    443        this.metadata = mc.getMetadataForId(modId, dummyMetadata);
    444        Loader.instance().computeDependencies(sortingProperties, getRequirements(), getDependencies(), getDependants());
    445    }
    446
    447    @Override
    448    public void setEnabledState(boolean enabled)
    449    {
    450        this.enabled = enabled;
    451    }
    452
    453    @Override
    454    public boolean registerBus(EventBus bus, LoadController controller)
    455    {
    456        if (this.enabled)
    457        {
    458            FMLLog.fine("Enabling mod %s", getModId());
    459            this.bus = bus;
    460            this.controller = controller;
    461            bus.register(this);
    462            return true;
    463        }
    464        else
    465        {
    466            return false;
    467        }
    468    }
    469
    470    // Lifecycle mod events
    471
    472    @Subscribe
    473    public void constructMod(FMLConstructionEvent event)
    474    {
    475        try
    476        {
    477            ModClassLoader modClassLoader = event.getModClassLoader();
    478            modClassLoader.addFile(modSource);
    479            EnumSet<TickType> ticks = EnumSet.noneOf(TickType.class);
    480            this.gameTickHandler = new BaseModTicker(ticks, false);
    481            this.guiTickHandler = new BaseModTicker(ticks.clone(), true);
    482            Class<? extends BaseModProxy> modClazz = (Class<? extends BaseModProxy>) modClassLoader.loadBaseModClass(modClazzName);
    483            configureMod(modClazz, event.getASMHarvestedData());
    484            isNetworkMod = FMLNetworkHandler.instance().registerNetworkMod(this, modClazz, event.getASMHarvestedData());
    485            ModLoaderNetworkHandler dummyHandler = null;
    486            if (!isNetworkMod)
    487            {
    488                FMLLog.fine("Injecting dummy network mod handler for BaseMod %s", getModId());
    489                dummyHandler = new ModLoaderNetworkHandler(this);
    490                FMLNetworkHandler.instance().registerNetworkMod(dummyHandler);
    491            }
    492            Constructor<? extends BaseModProxy> ctor = modClazz.getConstructor();
    493            ctor.setAccessible(true);
    494            mod = modClazz.newInstance();
    495            if (dummyHandler != null)
    496            {
    497                dummyHandler.setBaseMod(mod);
    498            }
    499            ProxyInjector.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide());
    500        }
    501        catch (Exception e)
    502        {
    503            controller.errorOccurred(this, e);
    504            Throwables.propagateIfPossible(e);
    505        }
    506    }
    507
    508    @Subscribe
    509    public void preInit(FMLPreInitializationEvent event)
    510    {
    511        try
    512        {
    513            this.gameTickHandler.setMod(mod);
    514            this.guiTickHandler.setMod(mod);
    515            TickRegistry.registerTickHandler(this.gameTickHandler, Side.CLIENT);
    516            TickRegistry.registerTickHandler(this.guiTickHandler, Side.CLIENT);
    517            GameRegistry.registerWorldGenerator(ModLoaderHelper.buildWorldGenHelper(mod));
    518            GameRegistry.registerFuelHandler(ModLoaderHelper.buildFuelHelper(mod));
    519            GameRegistry.registerCraftingHandler(ModLoaderHelper.buildCraftingHelper(mod));
    520            GameRegistry.registerPickupHandler(ModLoaderHelper.buildPickupHelper(mod));
    521            NetworkRegistry.instance().registerChatListener(ModLoaderHelper.buildChatListener(mod));
    522            NetworkRegistry.instance().registerConnectionHandler(ModLoaderHelper.buildConnectionHelper(mod));
    523        }
    524        catch (Exception e)
    525        {
    526            controller.errorOccurred(this, e);
    527            Throwables.propagateIfPossible(e);
    528        }
    529    }
    530
    531
    532    @Subscribe
    533    public void init(FMLInitializationEvent event)
    534    {
    535        try
    536        {
    537            mod.load();
    538        }
    539        catch (Throwable t)
    540        {
    541            controller.errorOccurred(this, t);
    542            Throwables.propagateIfPossible(t);
    543        }
    544    }
    545
    546    @Subscribe
    547    public void postInit(FMLPostInitializationEvent event)
    548    {
    549        try
    550        {
    551            mod.modsLoaded();
    552        }
    553        catch (Throwable t)
    554        {
    555            controller.errorOccurred(this, t);
    556            Throwables.propagateIfPossible(t);
    557        }
    558    }
    559
    560    @Subscribe
    561    public void loadComplete(FMLLoadCompleteEvent complete)
    562    {
    563        ModLoaderHelper.finishModLoading(this);
    564    }
    565
    566    @Subscribe
    567    public void serverStarting(FMLServerStartingEvent evt)
    568    {
    569        for (ICommand cmd : serverCommands)
    570        {
    571            evt.registerServerCommand(cmd);
    572        }
    573    }
    574    @Override
    575    public ArtifactVersion getProcessedVersion()
    576    {
    577        if (processedVersion == null)
    578        {
    579            processedVersion = new DefaultArtifactVersion(modId, getVersion());
    580        }
    581        return processedVersion;
    582    }
    583
    584    @Override
    585    public boolean isImmutable()
    586    {
    587        return false;
    588    }
    589
    590    @Override
    591    public boolean isNetworkMod()
    592    {
    593        return this.isNetworkMod;
    594    }
    595
    596    @Override
    597    public String getDisplayVersion()
    598    {
    599        return metadata!=null ? metadata.version : getVersion();
    600    }
    601
    602    public void addServerCommand(ICommand command)
    603    {
    604        serverCommands .add(command);
    605    }
    606
    607    @Override
    608    public VersionRange acceptableMinecraftVersionRange()
    609    {
    610        return Loader.instance().getMinecraftModContainer().getStaticVersionRange();
    611    }
    612    @Override
    613    public Certificate getSigningCertificate()
    614    {
    615        return null;
    616    }
    617}