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}